Skip to content

Commit 05cad10

Browse files
author
Pete Sramek
committed
added validation, added code comementsd
1 parent 34e61c0 commit 05cad10

File tree

1 file changed

+126
-51
lines changed

1 file changed

+126
-51
lines changed

src/PolylineAlgorithm.Abstraction/PolylineEncoding.cs

Lines changed: 126 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,25 @@ namespace PolylineAlgorithm.Abstraction;
1010
using System.Runtime.CompilerServices;
1111

1212
/// <summary>
13-
/// Provides methods for encoding and decoding geographic coordinates to and from polyline strings.
14-
/// Supports normalization, denormalization, and efficient value encoding/decoding for polyline algorithms.
13+
/// Provides methods for encoding and decoding polyline data, as well as utilities for normalizing and denormalizing
14+
/// geographic coordinate values.
1515
/// </summary>
16+
/// <remarks>The <see cref="PolylineEncoding"/> class includes functionality for working with encoded polyline
17+
/// data, such as reading and writing encoded values, as well as methods for normalizing and denormalizing geographic
18+
/// coordinates. It also provides validation utilities to ensure values conform to expected ranges for latitude and
19+
/// longitude.</remarks>
1620
public static class PolylineEncoding {
1721
/// <summary>
18-
/// Attempts to read an encoded value from the specified buffer and update the provided variance.
22+
/// Attempts to read a value from the specified buffer and updates the variance.
1923
/// </summary>
20-
/// <param name="variance">
21-
/// The current variance to update with the decoded value.
22-
/// </param>
23-
/// <param name="buffer">
24-
/// The buffer containing the encoded polyline data.
25-
/// </param>
26-
/// <param name="position">
27-
/// The current position in the buffer. This value is updated as the value is read.
28-
/// </param>
29-
/// <returns>
30-
/// <see langword="true"/> if a value was successfully read; otherwise, <see langword="false"/>.
31-
/// </returns>
24+
/// <remarks>This method processes the buffer starting at the specified position and attempts to decode a value.
25+
/// The decoded value is used to update the <paramref name="variance"/> parameter. The method stops reading when a
26+
/// termination condition is met or the end of the buffer is reached.</remarks>
27+
/// <param name="variance">A reference to the integer that will be updated based on the value read from the buffer.</param>
28+
/// <param name="buffer">A reference to the read-only memory buffer containing the data to be processed.</param>
29+
/// <param name="position">A reference to the current position within the buffer. The position is incremented as the method reads data.</param>
30+
/// <returns><see langword="true"/> if a value was successfully read and the end of the buffer was not reached; otherwise, <see
31+
/// langword="false"/>.</returns>
3232
[MethodImpl(MethodImplOptions.AggressiveInlining)]
3333
public static bool TryReadValue(ref int variance, ref ReadOnlyMemory<char> buffer, ref int position) {
3434
if (position == buffer.Length) {
@@ -56,32 +56,78 @@ public static bool TryReadValue(ref int variance, ref ReadOnlyMemory<char> buffe
5656
}
5757

5858
/// <summary>
59-
/// Converts a normalized integer value back to its original double representation.
59+
/// Converts a normalized integer value to its denormalized double representation based on the specified type.
6060
/// </summary>
61-
/// <param name="value">
62-
/// The normalized integer value to denormalize.
63-
/// </param>
64-
/// <returns>
65-
/// The denormalized double value.
66-
/// </returns>
61+
/// <remarks>The denormalization process divides the input value by a predefined precision factor to
62+
/// produce the resulting double. Ensure that <paramref name="value"/> is validated against the specified <paramref
63+
/// name="type"/> before calling this method.</remarks>
64+
/// <param name="value">The normalized integer value to be denormalized. Must be within the valid range for the specified <paramref
65+
/// name="type"/>.</param>
66+
/// <param name="type">The type that defines the valid range for <paramref name="value"/>.</param>
67+
/// <returns>The denormalized double representation of the input value. Returns <see langword="0.0"/> if <paramref
68+
/// name="value"/> is <see langword="0"/>.</returns>
69+
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> is outside the valid range for the specified <paramref name="type"/>.</exception>
6770
[MethodImpl(MethodImplOptions.AggressiveInlining)]
68-
public static double Denormalize(int value) => Math.Truncate((double)value) / Defaults.Algorithm.Precision;
71+
public static double Denormalize(int value, ValueType type) {
72+
if (!ValidateNormalizedValue(value, type)) {
73+
throw new ArgumentOutOfRangeException(nameof(value), value, "Value is out of range for the specified type.");
74+
}
75+
76+
if (value == 0) {
77+
return 0.0;
78+
}
79+
80+
return Math.Truncate((double)value) / Defaults.Algorithm.Precision;
81+
}
82+
83+
/// <summary>
84+
/// Validates whether the specified normalized value falls within the acceptable range for the given value type.
85+
/// </summary>
86+
/// <remarks>The valid range for normalized values depends on the specified <paramref name="type"/>: <list
87+
/// type="bullet"> <item> <description> For <see cref="ValueType.Latitude"/>, the value must be between
88+
/// <c>Defaults.Coordinate.Latitude.Normalized.Min</c> and <c>Defaults.Coordinate.Latitude.Normalized.Max</c>,
89+
/// inclusive. </description> </item> <item> <description> For <see cref="ValueType.Longitude"/>, the value must be
90+
/// between <c>Defaults.Coordinate.Longitude.Normalized.Min</c> and
91+
/// <c>Defaults.Coordinate.Longitude.Normalized.Max</c>, inclusive. </description> </item> </list> Any other
92+
/// <paramref name="type"/> will result in the method returning <see langword="false"/>.</remarks>
93+
/// <param name="value">The normalized value to validate. Must be an integer.</param>
94+
/// <param name="type">The type of value to validate, such as <see cref="ValueType.Latitude"/> or <see cref="ValueType.Longitude"/>.</param>
95+
/// <returns><see langword="true"/> if the normalized value is within the valid range for the specified value type;
96+
/// otherwise, <see langword="false"/>.</returns>
97+
public static bool ValidateNormalizedValue(int value, ValueType type) => (type, value) switch {
98+
(ValueType.Latitude, int normalized) when normalized >= Defaults.Coordinate.Latitude.Normalized.Min && normalized <= Defaults.Coordinate.Latitude.Normalized.Max => true,
99+
(ValueType.Longitude, int normalized) when normalized >= Defaults.Coordinate.Longitude.Normalized.Min && normalized <= Defaults.Coordinate.Longitude.Normalized.Max => true,
100+
_ => false,
101+
};
102+
103+
/// <summary>
104+
/// Validates whether the specified denormalized value falls within the acceptable range for the given value type.
105+
/// </summary>
106+
/// <remarks>The valid ranges for latitude and longitude are defined by <see
107+
/// cref="Defaults.Coordinate.Latitude.Min"/>, <see cref="Defaults.Coordinate.Latitude.Max"/>, <see
108+
/// cref="Defaults.Coordinate.Longitude.Min"/>, and <see cref="Defaults.Coordinate.Longitude.Max"/>.</remarks>
109+
/// <param name="value">The denormalized value to validate.</param>
110+
/// <param name="type">The type of value to validate, such as latitude or longitude.</param>
111+
/// <returns><see langword="true"/> if the <paramref name="value"/> is within the valid range for the specified <paramref
112+
/// name="type"/>; otherwise, <see langword="false"/>.</returns>
113+
public static bool ValidateDenormalizedValue(double value, ValueType type) => (type, value) switch {
114+
(ValueType.Latitude, double denormalized) when denormalized >= Defaults.Coordinate.Latitude.Min && denormalized <= Defaults.Coordinate.Latitude.Max => true,
115+
(ValueType.Longitude, double denormalized) when denormalized >= Defaults.Coordinate.Longitude.Min && denormalized <= Defaults.Coordinate.Longitude.Max => true,
116+
_ => false,
117+
};
69118

70119
/// <summary>
71-
/// Attempts to write an encoded value to the specified buffer.
120+
/// Attempts to write a value derived from the specified <paramref name="variance"/> into the provided <paramref
121+
/// name="buffer"/> at the given <paramref name="position"/>.
72122
/// </summary>
73-
/// <param name="variance">
74-
/// The variance value to encode.
75-
/// </param>
76-
/// <param name="buffer">
77-
/// The buffer to write the encoded value to.
78-
/// </param>
79-
/// <param name="position">
80-
/// The current position in the buffer. This value is updated as the value is written.
81-
/// </param>
82-
/// <returns>
83-
/// <see langword="true"/> if the value was successfully written; otherwise, <see langword="false"/>.
84-
/// </returns>
123+
/// <remarks>This method performs bounds checking to ensure that the buffer has sufficient space to
124+
/// accommodate the calculated value. If the buffer does not have enough space, the method returns <see
125+
/// langword="false"/> without modifying the buffer or position.</remarks>
126+
/// <param name="variance">The integer value used to calculate the output to be written into the buffer.</param>
127+
/// <param name="buffer">A reference to the span of characters where the value will be written.</param>
128+
/// <param name="position">A reference to the current position in the buffer where writing begins. This value is updated to reflect the new
129+
/// position after writing.</param>
130+
/// <returns><see langword="true"/> if the value was successfully written to the buffer; otherwise, <see langword="false"/>.</returns>
85131
[MethodImpl(MethodImplOptions.AggressiveInlining)]
86132
public static bool TryWriteValue(int variance, ref Span<char> buffer, ref int position) {
87133
if (buffer.Length < position + GetCharCount(variance)) {
@@ -105,26 +151,44 @@ public static bool TryWriteValue(int variance, ref Span<char> buffer, ref int po
105151
}
106152

107153
/// <summary>
108-
/// Normalizes a double value into an integer representation suitable for polyline encoding.
154+
/// Normalizes a given numeric value based on the specified type and precision settings.
109155
/// </summary>
110-
/// <param name="value">
111-
/// The double value to normalize.
112-
/// </param>
113-
/// <returns>
114-
/// The normalized integer value.
115-
/// </returns>
156+
/// <remarks>This method validates the input value to ensure it is finite and within the acceptable range
157+
/// for the specified type. If the value is valid, it applies a normalization algorithm using a predefined precision
158+
/// factor.</remarks>
159+
/// <param name="value">The numeric value to normalize. Must be a finite number.</param>
160+
/// <param name="type">The type against which the value is validated. Determines the acceptable range for the value.</param>
161+
/// <returns>An integer representing the normalized value. Returns <c>0</c> if the input value is <c>0.0</c>.</returns>
162+
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> is not a finite number or is outside the valid range for the specified
163+
/// <paramref name="type"/>.</exception>
116164
[MethodImpl(MethodImplOptions.AggressiveInlining)]
117-
public static int Normalize(double value) => (int)Math.Round(value * Defaults.Algorithm.Precision);
165+
public static int Normalize(double value, ValueType type) {
166+
if (double.IsNaN(value) || double.IsInfinity(value)) {
167+
throw new ArgumentOutOfRangeException(nameof(value), "Value must be a finite number.");
168+
}
169+
170+
if (!ValidateDenormalizedValue(value, type)) {
171+
throw new ArgumentOutOfRangeException(nameof(value), value, "Value is out of range for the specified type.");
172+
}
173+
174+
if (value == 0.0) {
175+
return 0;
176+
}
177+
178+
return (int)Math.Round(value * Defaults.Algorithm.Precision);
179+
}
118180

119181
/// <summary>
120-
/// Calculates the number of characters required to encode a given variance value.
182+
/// Determines the number of characters required to represent the specified integer value within predefined
183+
/// variance ranges.
121184
/// </summary>
122-
/// <param name="variance">
123-
/// The variance value to encode.
124-
/// </param>
125-
/// <returns>
126-
/// The number of characters required to encode the variance.
127-
/// </returns>
185+
/// <remarks>The method uses predefined ranges to efficiently determine the character count. Smaller
186+
/// values require fewer characters, while larger values require more. This method is optimized for performance
187+
/// using a switch expression.</remarks>
188+
/// <param name="variance">The integer value for which the character count is calculated. Must be within the range of a 32-bit signed
189+
/// integer.</param>
190+
/// <returns>The number of characters required to represent the <paramref name="variance"/> value, based on its magnitude.
191+
/// Returns a value between 1 and 6 inclusive.</returns>
128192
[MethodImpl(MethodImplOptions.AggressiveInlining)]
129193
public static int GetCharCount(int variance) => variance switch {
130194
// DO NOT CHANGE THE ORDER. We are skipping inside exclusive ranges as those are covered by previous statements.
@@ -135,4 +199,15 @@ public static bool TryWriteValue(int variance, ref Span<char> buffer, ref int po
135199
>= -16777216 and <= +16777215 => 5,
136200
_ => 6,
137201
};
202+
203+
/// <summary>
204+
/// Represents the type of a geographic coordinate value.
205+
/// </summary>
206+
/// <remarks>This enumeration is used to specify whether a coordinate value represents latitude or
207+
/// longitude. Latitude values indicate the north-south position, while longitude values indicate the east-west
208+
/// position.</remarks>
209+
public enum ValueType {
210+
Latitude,
211+
Longitude
212+
}
138213
}

0 commit comments

Comments
 (0)