Skip to content

Commit 5af6648

Browse files
Copilotpetesramek
andauthored
fix: restore Coordinate.cs and Polyline.cs, fix usings in AbstractPolylineDecoder, fix typo in AbstractPolylineEncoder, add default CancellationToken to Decode
- Restore Coordinate.cs and Polyline.cs accidentally deleted in the 241880e commit - Remove invalid 'using PolylineAlgorithm.Diagnostics' and duplicate using from AbstractPolylineDecoder - Fix typo ThrwoArgumentCannotBeEmptyEnumerationMessage -> ThrowArgumentCannotBeEmptyEnumerationMessage - Add = default to AbstractPolylineDecoder.Decode cancellationToken to restore source compatibility Agent-Logs-Url: https://github.com/petesramek/polyline-algorithm-csharp/sessions/05118682-83dd-4b46-adee-22711f38696f Co-authored-by: petesramek <2333452+petesramek@users.noreply.github.com>
1 parent b79d2ee commit 5af6648

4 files changed

Lines changed: 447 additions & 4 deletions

File tree

src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@
44
//
55

66
using Microsoft.Extensions.Logging;
7-
using PolylineAlgorithm.Diagnostics;
87
using PolylineAlgorithm.Internal;
98
using PolylineAlgorithm.Internal.Diagnostics;
10-
using PolylineAlgorithm.Internal.Diagnostics;
119
using System.Runtime.CompilerServices;
1210

1311
namespace PolylineAlgorithm.Abstraction {
@@ -79,7 +77,7 @@ protected AbstractPolylineDecoder(PolylineEncodingOptions options) {
7977
/// <exception cref="OperationCanceledException">
8078
/// Thrown when <paramref name="cancellationToken"/> is canceled during decoding.
8179
/// </exception>
82-
public IEnumerable<TCoordinate> Decode(TPolyline polyline, CancellationToken cancellationToken) {
80+
public IEnumerable<TCoordinate> Decode(TPolyline polyline, CancellationToken cancellationToken = default) {
8381
const string OperationName = nameof(Decode);
8482

8583
_logger?.LogOperationStartedDebug(OperationName);

src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ static void ValidateEmptyCoordinates(ref ReadOnlySpan<TCoordinate> coordinates,
151151
logger
152152
.LogEmptyArgumentWarning(nameof(coordinates));
153153

154-
ExceptionGuard.ThrwoArgumentCannotBeEmptyEnumerationMessage(nameof(coordinates));
154+
ExceptionGuard.ThrowArgumentCannotBeEmptyEnumerationMessage(nameof(coordinates));
155155
}
156156
}
157157
}
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
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;
7+
8+
using PolylineAlgorithm.Internal.Diagnostics;
9+
using System;
10+
using System.Diagnostics;
11+
using System.Globalization;
12+
using System.Runtime.CompilerServices;
13+
using System.Runtime.InteropServices;
14+
15+
/// <summary>
16+
/// Represents a geographic coordinate as a pair of latitude and longitude values.
17+
/// </summary>
18+
/// <remarks>
19+
/// This struct is designed to be immutable and lightweight, providing a simple way to represent
20+
/// geographic coordinates in degrees. It includes validation for latitude and longitude ranges
21+
/// and provides methods for equality comparison and string representation.
22+
///
23+
/// <para>Note: The value (0, 0) is a valid coordinate (Gulf of Guinea), but is also treated as the "default" value by <see cref="IsDefault"/>.</para>
24+
/// </remarks>
25+
[DebuggerDisplay("{ToString()}")]
26+
[StructLayout(LayoutKind.Auto)]
27+
public readonly struct Coordinate : IEquatable<Coordinate> {
28+
/// <summary>
29+
/// Initializes a new instance of the <see cref="Coordinate"/> struct with default values (0) for <see cref="Latitude"/> and <see cref="Longitude"/>.
30+
/// </summary>
31+
/// <remarks>
32+
/// The default value (0, 0) is a valid coordinate (Gulf of Guinea), but is also treated as the "default" value by <see cref="IsDefault"/>.
33+
/// </remarks>
34+
public Coordinate() {
35+
Latitude = default;
36+
Longitude = default;
37+
}
38+
39+
/// <summary>
40+
/// Initializes a new instance of the <see cref="Coordinate"/> struct with the specified latitude and longitude values.
41+
/// </summary>
42+
/// <param name="latitude">
43+
/// The latitude component of the coordinate, in degrees. Must be between -90 and 90.
44+
/// </param>
45+
/// <param name="longitude">
46+
/// The longitude component of the coordinate, in degrees. Must be between -180 and 180.
47+
/// </param>
48+
/// <exception cref="ArgumentOutOfRangeException">
49+
/// Thrown when <paramref name="latitude"/> is less than -90 or greater than 90,
50+
/// or when <paramref name="longitude"/> is less than -180 or greater than 180.
51+
/// </exception>
52+
public Coordinate(double latitude, double longitude) {
53+
Validator.Validate(latitude, longitude);
54+
55+
Latitude = latitude;
56+
Longitude = longitude;
57+
}
58+
59+
/// <summary>
60+
/// Gets the latitude component of the coordinate, in degrees.
61+
/// </summary>
62+
public double Latitude { get; }
63+
64+
/// <summary>
65+
/// Gets the longitude component of the coordinate, in degrees.
66+
/// </summary>
67+
public double Longitude { get; }
68+
69+
/// <summary>
70+
/// Determines whether this coordinate is the default value (both <see cref="Latitude"/> and <see cref="Longitude"/> are 0).
71+
/// </summary>
72+
/// <remarks>
73+
/// The value (0, 0) is a valid coordinate (Gulf of Guinea), but is also treated as the "default" value by this method.
74+
/// </remarks>
75+
/// <returns>
76+
/// <see langword="true"/> if both latitude and longitude are 0; otherwise, <see langword="false"/>.
77+
/// </returns>
78+
public bool IsDefault()
79+
=> Latitude.Equals(default)
80+
&& Longitude.Equals(default);
81+
82+
/// <inheritdoc />
83+
public override bool Equals(object? obj) => obj is Coordinate coordinate && Equals(coordinate);
84+
85+
/// <inheritdoc />
86+
public bool Equals(Coordinate other) {
87+
return Latitude.Equals(other.Latitude) &&
88+
Longitude.Equals(other.Longitude);
89+
}
90+
91+
/// <summary>
92+
/// Determines whether two <see cref="Coordinate"/> instances are approximately equal within a specified tolerance.
93+
/// </summary>
94+
/// <param name="other">The other coordinate to compare.</param>
95+
/// <param name="tolerance">The maximum allowed difference for latitude and longitude.</param>
96+
/// <returns>
97+
/// <see langword="true"/> if both latitude and longitude differ by less than <paramref name="tolerance"/>; otherwise, <see langword="false"/>.
98+
/// </returns>
99+
public bool Equals(Coordinate other, double tolerance)
100+
=> Math.Abs(Latitude - other.Latitude) < tolerance
101+
&& Math.Abs(Longitude - other.Longitude) < tolerance;
102+
103+
/// <inheritdoc />
104+
public override int GetHashCode() => HashCode.Combine(Latitude, Longitude);
105+
106+
/// <summary>
107+
/// Returns a string representation of this coordinate in the format: <c>{ Latitude: [double], Longitude: [double] }</c>.
108+
/// </summary>
109+
/// <returns>
110+
/// A string representation of the coordinate.
111+
/// </returns>
112+
public override string ToString() {
113+
return $"{{ {nameof(Latitude)}: {Latitude.ToString("G", CultureInfo.InvariantCulture)}, {nameof(Longitude)}: {Longitude.ToString("G", CultureInfo.InvariantCulture)} }}";
114+
}
115+
116+
/// <summary>
117+
/// Determines whether two <see cref="Coordinate"/> instances are equal.
118+
/// </summary>
119+
/// <param name="left">The first coordinate to compare.</param>
120+
/// <param name="right">The second coordinate to compare.</param>
121+
/// <returns>
122+
/// <see langword="true"/> if both coordinates are equal; otherwise, <see langword="false"/>.
123+
/// </returns>
124+
public static bool operator ==(Coordinate left, Coordinate right) => left.Equals(right);
125+
126+
/// <summary>
127+
/// Determines whether two <see cref="Coordinate"/> instances are not equal.
128+
/// </summary>
129+
/// <param name="left">The first coordinate to compare.</param>
130+
/// <param name="right">The second coordinate to compare.</param>
131+
/// <returns>
132+
/// <see langword="true"/> if the coordinates are not equal; otherwise, <see langword="false"/>.
133+
/// </returns>
134+
public static bool operator !=(Coordinate left, Coordinate right) => !(left == right);
135+
136+
/// <summary>
137+
/// Provides static methods for validating latitude and longitude values used in <see cref="Coordinate"/>.
138+
/// </summary>
139+
/// <remarks>
140+
/// The <c>Validator</c> class ensures that latitude and longitude values are within their valid ranges:
141+
/// <list type="bullet">
142+
/// <item>
143+
/// <description>Latitude must be between -90 and 90 degrees.</description>
144+
/// </item>
145+
/// <item>
146+
/// <description>Longitude must be between -180 and 180 degrees.</description>
147+
/// </item>
148+
/// </list>
149+
/// If a value is out of range or not finite, an exception is thrown via <see cref="ExceptionGuard.ThrowCoordinateValueOutOfRange"/>.
150+
/// </remarks>
151+
public static class Validator {
152+
/// <summary>
153+
/// Validates that the specified latitude is within the valid range of -90 to 90 degrees.
154+
/// </summary>
155+
/// <param name="latitude">The latitude value to validate.</param>
156+
/// <exception cref="ArgumentOutOfRangeException">
157+
/// Thrown if <paramref name="latitude"/> is less than -90, greater than 90, or not a finite number.
158+
/// </exception>
159+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
160+
public static void ValidateLatitude(double latitude) {
161+
const double min = -90;
162+
const double max = 90;
163+
164+
ValidateValue(latitude, min, max, nameof(latitude));
165+
}
166+
167+
/// <summary>
168+
/// Validates that the specified longitude is within the valid range of -180 to 180 degrees.
169+
/// </summary>
170+
/// <param name="longitude">The longitude value to validate.</param>
171+
/// <exception cref="ArgumentOutOfRangeException">
172+
/// Thrown if <paramref name="longitude"/> is less than -180, greater than 180, or not a finite number.
173+
/// </exception>
174+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
175+
public static void ValidateLongitude(double longitude) {
176+
const double min = -180;
177+
const double max = 180;
178+
179+
ValidateValue(longitude, min, max, nameof(longitude));
180+
}
181+
182+
/// <summary>
183+
/// Validates that the specified value is finite and within the specified range.
184+
/// </summary>
185+
/// <param name="value">The value to validate.</param>
186+
/// <param name="min">The minimum allowed value (inclusive).</param>
187+
/// <param name="max">The maximum allowed value (inclusive).</param>
188+
/// <param name="paramName">The name of the parameter being validated.</param>
189+
/// <exception cref="ArgumentOutOfRangeException">
190+
/// Thrown if <paramref name="value"/> is not finite, less than <paramref name="min"/>, or greater than <paramref name="max"/>.
191+
/// </exception>
192+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
193+
public static void ValidateValue(double value, double min, double max, string paramName) {
194+
if (!double.IsFinite(value) || value < min || value > max) {
195+
ExceptionGuard.ThrowCoordinateValueOutOfRange(value, min, max, paramName);
196+
}
197+
}
198+
199+
/// <summary>
200+
/// Validates both latitude and longitude values for a coordinate.
201+
/// </summary>
202+
/// <param name="latitude">The latitude value to validate.</param>
203+
/// <param name="longitude">The longitude value to validate.</param>
204+
/// <exception cref="ArgumentOutOfRangeException">
205+
/// Thrown if either <paramref name="latitude"/> or <paramref name="longitude"/> is out of range or not finite.
206+
/// </exception>
207+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
208+
internal static void Validate(double latitude, double longitude) {
209+
ValidateLatitude(latitude);
210+
ValidateLongitude(longitude);
211+
}
212+
}
213+
}

0 commit comments

Comments
 (0)