@@ -10,25 +10,25 @@ namespace PolylineAlgorithm.Abstraction;
10
10
using System . Runtime . CompilerServices ;
11
11
12
12
/// <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 .
15
15
/// </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>
16
20
public static class PolylineEncoding {
17
21
/// <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.
19
23
/// </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>
32
32
[ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
33
33
public static bool TryReadValue ( ref int variance , ref ReadOnlyMemory < char > buffer , ref int position ) {
34
34
if ( position == buffer . Length ) {
@@ -56,32 +56,78 @@ public static bool TryReadValue(ref int variance, ref ReadOnlyMemory<char> buffe
56
56
}
57
57
58
58
/// <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 .
60
60
/// </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>
67
70
[ 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
+ } ;
69
118
70
119
/// <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"/>.
72
122
/// </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>
85
131
[ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
86
132
public static bool TryWriteValue ( int variance , ref Span < char > buffer , ref int position ) {
87
133
if ( buffer . Length < position + GetCharCount ( variance ) ) {
@@ -105,26 +151,44 @@ public static bool TryWriteValue(int variance, ref Span<char> buffer, ref int po
105
151
}
106
152
107
153
/// <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 .
109
155
/// </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>
116
164
[ 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
+ }
118
180
119
181
/// <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.
121
184
/// </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>
128
192
[ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
129
193
public static int GetCharCount ( int variance ) => variance switch {
130
194
// 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
135
199
>= - 16777216 and <= + 16777215 => 5 ,
136
200
_ => 6 ,
137
201
} ;
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
+ }
138
213
}
0 commit comments