Skip to content

Commit c41a00d

Browse files
author
Petr Sramek
committed
refactored polyline algorithm
1 parent 5de7c4d commit c41a00d

File tree

5 files changed

+111
-115
lines changed

5 files changed

+111
-115
lines changed

src/Encoding/PolylineEncodingBase.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,13 @@ public abstract class PolylineEncodingBase<T> : IPolylineEncoding<T>
2020
/// </summary>
2121
/// <param name="source">The <see cref="string"/></param>
2222
/// <returns>The <see cref="IEnumerable{T}"/></returns>
23-
public IEnumerable<T> Decode(string source)
23+
public IEnumerable<T> Decode(string polyline)
2424
{
25-
if (string.IsNullOrWhiteSpace(source))
25+
if (string.IsNullOrWhiteSpace(polyline))
2626
{
27-
throw new ArgumentException(ExceptionMessageResource.ArgumentCannotBeNullOrEmpty, nameof(source));
27+
throw new ArgumentException(ExceptionMessageResource.ArgumentCannotBeNullOrEmpty, nameof(polyline));
2828
}
2929

30-
char[] polyline = source.ToCharArray();
31-
3230
return PolylineAlgorithm.Decode(polyline)
3331
.Select(c => CreateResult(c.Latitude, c.Longitude));
3432
}

src/ExceptionMessageResource.Designer.cs

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/ExceptionMessageResource.resx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,8 @@
123123
<data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve">
124124
<value>Argument cannot be null -or- empty char array.</value>
125125
</data>
126-
<data name="ArgumentExceptionCoordinateIsOutOfRangeErrorMessageFormat" xml:space="preserve">
127-
<value>Latitude: {0}, Longitude: {1}) is invalid. Latitude must be in range between - 90 and +90. Longitude must be in range between -180 and +180.</value>
126+
<data name="CoordinateValidationExceptionCoordinateIsOutOfRangeErrorMessageFormat" xml:space="preserve">
127+
<value>Latitude {0} or longitude {1} is not valid. Latitude must be in range between -90 and +90. Longitude must be in range between -180 and +180.</value>
128128
</data>
129129
<data name="PolylineCharArrayIsMalformed" xml:space="preserve">
130130
<value>Polyline is malformed.</value>

src/PolylineAlgorithm.cs

Lines changed: 75 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ public static class PolylineAlgorithm
2626
/// <returns>Returns coordinates.</returns>
2727
/// <exception cref="ArgumentException">If polyline argument is null -or- empty char array.</exception>
2828
/// <exception cref="InvalidOperationException">If polyline representation is not in correct format.</exception>
29-
public static IEnumerable<(double Latitude, double Longitude)> Decode(char[] polyline)
29+
public static IEnumerable<(double Latitude, double Longitude)> Decode(string polyline)
3030
{
3131
// Checking null and at least one character
32-
if (polyline == null || !polyline.Any())
32+
if (polyline == null || polyline.Length == 0)
3333
{
3434
throw new ArgumentException(ExceptionMessageResource.ArgumentCannotBeNullOrEmpty, nameof(polyline));
3535
}
@@ -54,14 +54,46 @@ public static class PolylineAlgorithm
5454
throw new InvalidOperationException(ExceptionMessageResource.PolylineCharArrayIsMalformed);
5555
}
5656

57-
var coordinate = (GetDoubleRepresentation(latitude), GetDoubleRepresentation(longitude));
57+
var coordinate = (GetCoordinate(latitude), GetCoordinate(longitude));
5858

59+
// Validating decoded coordinate. If not valid exception is thrown
5960
if (!CoordinateValidator.IsValid(coordinate))
6061
{
6162
throw new InvalidOperationException(ExceptionMessageResource.PolylineCharArrayIsMalformed);
6263
}
6364

6465
yield return coordinate;
66+
67+
#region Local functions
68+
69+
bool TryCalculateNext(string polyline, ref int index, ref int value)
70+
{
71+
// Local variable initialization
72+
int chunk;
73+
int sum = 0;
74+
int shifter = 0;
75+
76+
do
77+
{
78+
chunk = polyline[index++] - Constants.ASCII.QuestionMark;
79+
sum |= (chunk & Constants.ASCII.UnitSeparator) << shifter;
80+
shifter += Constants.ShiftLength;
81+
} while (chunk >= Constants.ASCII.Space && index < polyline.Length);
82+
83+
if (index >= polyline.Length && chunk >= Constants.ASCII.Space)
84+
return false;
85+
86+
value += (sum & 1) == 1 ? ~(sum >> 1) : sum >> 1;
87+
88+
return true;
89+
}
90+
91+
double GetCoordinate(int value)
92+
{
93+
return Convert.ToDouble(value) / Constants.Precision;
94+
}
95+
96+
#endregion
6597
}
6698
}
6799

@@ -74,139 +106,78 @@ public static class PolylineAlgorithm
74106
/// <exception cref="AggregateException">If one or more coordinate is out of range</exception>
75107
public static string Encode(IEnumerable<(double Latitude, double Longitude)> coordinates)
76108
{
77-
if (coordinates == null || !coordinates.Any())
109+
if (coordinates == null || !coordinates.GetEnumerator().MoveNext())
78110
{
79111
throw new ArgumentException(ExceptionMessageResource.ArgumentCannotBeNullOrEmpty, nameof(coordinates));
80112
}
81113

82-
// Ensuring coordinates are valid, otherwise throws an aggregate exception
83-
EnsureCoordinates(coordinates);
114+
// Validate collection of coordinates
115+
if (!TryValidate(coordinates, out var exceptions))
116+
{
117+
throw new AggregateException(exceptions);
118+
}
84119

85120
// Initializing local variables
86121
int previousLatitude = 0;
87122
int previousLongitude = 0;
88-
var sb = _pool.Get();
123+
var sb = new StringBuilder(coordinates.Count() * 5);
89124

90125
// Looping over coordinates and building encoded result
91126
foreach (var coordinate in coordinates)
92127
{
93-
int latitude = GetIntegerRepresentation(coordinate.Latitude);
94-
int longitude = GetIntegerRepresentation(coordinate.Longitude);
128+
int latitude = Round(coordinate.Latitude);
129+
int longitude = Round(coordinate.Longitude);
95130

96-
sb.Append(GetEncodedCharacters(latitude - previousLatitude).ToArray());
97-
sb.Append(GetEncodedCharacters(longitude - previousLongitude).ToArray());
131+
sb.Append(GetSequence(latitude - previousLatitude).ToArray());
132+
sb.Append(GetSequence(longitude - previousLongitude).ToArray());
98133

99134
previousLatitude = latitude;
100135
previousLongitude = longitude;
101136
}
102137

103-
var result = sb.ToString();
104-
105-
_pool.Return(sb);
106-
107-
return result;
108-
}
138+
return sb.ToString();
109139

110-
/// <summary>
111-
/// Method performs coordinates validation. Throws exception, if invalid coordinate is found
112-
/// </summary>
113-
/// <param name="coordinates">Coordinates to validate</param>
114-
/// <exception cref="AggregateException">If one or more coordinate is out of range -or- invalid</exception>
115-
private static void EnsureCoordinates(IEnumerable<(double Latitude, double Longitude)> coordinates)
116-
{
117-
// Selecting invalid coordinates
118-
var invalidCoordinates = coordinates
119-
.Where(c => !CoordinateValidator.IsValid(c));
140+
#region Local functions
120141

121-
// If any invalid coordinates exists throw an aggregate exception with inner argument out of range exception
122-
if (invalidCoordinates.Any())
142+
bool TryValidate(IEnumerable<(double Latitude, double Longitude)> collection, out ICollection<CoordinateValidationException> exceptions)
123143
{
124-
throw new AggregateException(
125-
ExceptionMessageResource.AggregateExceptionCoordinatesAreInvalidErrorMessage,
126-
invalidCoordinates
127-
.Select(c =>
128-
new ArgumentOutOfRangeException(
129-
string.Format(
130-
ExceptionMessageResource.ArgumentExceptionCoordinateIsOutOfRangeErrorMessageFormat,
131-
c.Latitude,
132-
c.Longitude
133-
)
134-
)
135-
)
136-
);
137-
}
138-
}
144+
exceptions = new List<CoordinateValidationException>(collection.Count());
139145

140-
/// <summary>
141-
///
142-
/// </summary>
143-
/// <param name="value">Rounded integer representation of precise double value</param>
144-
/// <returns>Returns value with specific precision. See <see cref="Constants.Precision"/></returns>
145-
private static double GetDoubleRepresentation(int value)
146-
{
147-
return Convert.ToDouble(value) / Constants.Precision;
148-
}
149-
150-
/// <summary>
151-
/// Method converts value to polyline encoded characters
152-
/// </summary>
153-
/// <param name="value">Difference between current and previous latitude or longitude value</param>
154-
private static IEnumerable<char> GetEncodedCharacters(int value)
155-
{
156-
int shifted = value << 1;
157-
if (value < 0)
158-
shifted = ~shifted;
146+
foreach (var item in collection)
147+
{
148+
if (!CoordinateValidator.IsValid(item))
149+
{
150+
exceptions.Add(new CoordinateValidationException(item.Latitude, item.Longitude));
151+
}
152+
}
159153

160-
int rem = shifted;
154+
return !exceptions.GetEnumerator().MoveNext();
155+
}
161156

162-
while (rem >= Constants.ASCII.Space)
157+
int Round(double value)
163158
{
164-
yield return (char)((Constants.ASCII.Space | rem & Constants.ASCII.UnitSeparator) + Constants.ASCII.QuestionMark);
165-
166-
rem >>= Constants.ShiftLength;
159+
return (int)Math.Round(value * Constants.Precision);
167160
}
168161

169-
yield return (char)(rem + Constants.ASCII.QuestionMark);
170-
}
171-
172-
/// <summary>
173-
/// Method
174-
/// </summary>
175-
/// <param name="value">Precise double representation</param>
176-
/// <returns></returns>
177-
private static int GetIntegerRepresentation(double value)
178-
{
179-
return (int)Math.Round(value * Constants.Precision);
180-
}
181-
182-
/// <summary>
183-
/// Tries to calculate next integer representation of encoded polyline part
184-
/// </summary>
185-
/// <param name="polyline">The <see cref="char[]"/></param>
186-
/// <param name="index">The <see cref="int"/></param>
187-
/// <param name="value">The <see cref="int"/></param>
188-
/// <returns>The <see cref="bool"/></returns>
189-
private static bool TryCalculateNext(char[] polyline, ref int index, ref int value)
190-
{
191-
// Local variable initialization
192-
int chunk;
193-
int sum = 0;
194-
int shifter = 0;
162+
IEnumerable<char> GetSequence(int value)
163+
{
164+
int shifted = value << 1;
165+
if (value < 0)
166+
shifted = ~shifted;
195167

168+
int rem = shifted;
196169

197-
do
198-
{
199-
chunk = polyline[index++] - Constants.ASCII.QuestionMark;
200-
sum |= (chunk & Constants.ASCII.UnitSeparator) << shifter;
201-
shifter += Constants.ShiftLength;
202-
} while (chunk >= Constants.ASCII.Space && index < polyline.Length);
170+
while (rem >= Constants.ASCII.Space)
171+
{
172+
yield return (char)((Constants.ASCII.Space | rem & Constants.ASCII.UnitSeparator) + Constants.ASCII.QuestionMark);
203173

204-
if (index >= polyline.Length && chunk >= Constants.ASCII.Space)
205-
return false;
174+
rem >>= Constants.ShiftLength;
175+
}
206176

207-
value += (sum & 1) == 1 ? ~(sum >> 1) : sum >> 1;
177+
yield return (char)(rem + Constants.ASCII.QuestionMark);
178+
}
208179

209-
return true;
180+
#endregion
210181
}
211182
}
212183
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//
2+
// Copyright (c) Petr Šrámek. All rights reserved.
3+
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
4+
//
5+
6+
namespace DropoutCoder.PolylineAlgorithm.Validation
7+
{
8+
using System;
9+
10+
/// <summary>
11+
/// The exception that is thrown when one of the coordinates is not valid.
12+
/// </summary>
13+
public class CoordinateValidationException : Exception
14+
{
15+
/// <summary>
16+
/// Initializes a new instance of the <see cref="CoordinateValidationException"/> class with an error message and invalid coordinate values.
17+
/// </summary>
18+
/// <param name="latitude">The latitude value of invalid coodinate</param>
19+
/// <param name="longitude">The longitude value of invalid coodinate</param>
20+
public CoordinateValidationException(double latitude, double longitude)
21+
: base(string.Format(ExceptionMessageResource.CoordinateValidationExceptionCoordinateIsOutOfRangeErrorMessageFormat, latitude, longitude)) { }
22+
23+
public double Latitude { get; }
24+
25+
public double Longitude { get; }
26+
}
27+
}

0 commit comments

Comments
 (0)