|
| 1 | +using System; |
| 2 | +using System.Collections.Generic; |
| 3 | +using Google.OpenLocationCode; |
| 4 | +using NUnit.Framework; |
| 5 | + |
| 6 | +public static class EncodingTest { |
| 7 | + |
| 8 | + // Test cases for encoding latitude and longitude to codes and expected |
| 9 | + // https://github.com/google/open-location-code/blob/master/test_data/encodingTests.csv |
| 10 | + private static readonly List<TestData> TestDataList = new List<TestData> { |
| 11 | + new TestData("7FG49Q00+", "7FG49Q", 20.375, 2.775, 20.35, 2.75, 20.4, 2.8), |
| 12 | + new TestData("7FG49QCJ+2V", "7FG49QCJ2V", 20.3700625, 2.7821875, 20.37, 2.782125, 20.370125, 2.78225), |
| 13 | + new TestData("7FG49QCJ+2VX", "7FG49QCJ2VX", 20.3701125, 2.782234375, 20.3701, 2.78221875, 20.370125, 2.78225), |
| 14 | + new TestData("7FG49QCJ+2VXGJ", "7FG49QCJ2VXGJ", 20.3701135, 2.78223535156, 20.370113, 2.782234375, 20.370114, 2.78223632813), |
| 15 | + new TestData("8FVC2222+22", "8FVC222222", 47.0000625, 8.0000625, 47.0, 8.0, 47.000125, 8.000125), |
| 16 | + new TestData("4VCPPQGP+Q9", "4VCPPQGPQ9", -41.2730625, 174.7859375, -41.273125, 174.785875, -41.273, 174.786), |
| 17 | + new TestData("62G20000+", "62G2", 0.5, -179.5, 0.0, -180.0, 1, -179), |
| 18 | + new TestData("22220000+", "2222", -89.5, -179.5, -90, -180, -89, -179), |
| 19 | + new TestData("7FG40000+", "7FG4", 20.5, 2.5, 20.0, 2.0, 21.0, 3.0), |
| 20 | + new TestData("22222222+22", "2222222222", -89.9999375, -179.9999375, -90.0, -180.0, -89.999875, -179.999875), |
| 21 | + new TestData("6VGX0000+", "6VGX", 0.5, 179.5, 0, 179, 1, 180), |
| 22 | + new TestData("6FH32222+222", "6FH32222222", 1, 1, 1, 1, 1.000025, 1.00003125), |
| 23 | + // Special cases over 90 latitude and 180 longitude |
| 24 | + new TestData("CFX30000+", "CFX3", 90, 1, 89, 1, 90, 2), |
| 25 | + new TestData("CFX30000+", "CFX3", 92, 1, 89, 1, 90, 2), |
| 26 | + new TestData("62H20000+", "62H2", 1, 180, 1, -180, 2, -179), |
| 27 | + new TestData("62H30000+", "62H3", 1, 181, 1, -179, 2, -178), |
| 28 | + new TestData("CFX3X2X2+X2", "CFX3X2X2X2", 90, 1, 89.9998750, 1, 90, 1.0001250), |
| 29 | + // Test non-precise latitude/longitude value |
| 30 | + new TestData("6FH56C22+22", "6FH56C2222", 1.2, 3.4, 1.2000000000000028, 3.4000000000000057, 1.2001249999999999, 3.4001250000000027) |
| 31 | + }; |
| 32 | + |
| 33 | + public class TheEncodeMethod { |
| 34 | + [Test] |
| 35 | + public void ShouldEncodePointToExpectedLocationCode() { |
| 36 | + foreach (var testData in TestDataList) { |
| 37 | + Assert.AreEqual(testData.Code, OpenLocationCode.Encode(testData.EncodedLatitude, testData.EncodedLongitude, testData.CodeDigits.Length), |
| 38 | + $"Latitude {testData.EncodedLatitude} and longitude {testData.EncodedLongitude} were wrongly encoded."); |
| 39 | + } |
| 40 | + } |
| 41 | + |
| 42 | + [Test] |
| 43 | + public void ShouldClipCoordinatesWhenExceedingMaximum() { |
| 44 | + Assert.AreEqual(OpenLocationCode.Encode(-90, 5), OpenLocationCode.Encode(-91, 5), |
| 45 | + "Clipping of negative latitude doesn't work."); |
| 46 | + Assert.AreEqual(OpenLocationCode.Encode(90, 5), OpenLocationCode.Encode(91, 5), |
| 47 | + "Clipping of positive latitude doesn't work."); |
| 48 | + Assert.AreEqual(OpenLocationCode.Encode(5, 175), OpenLocationCode.Encode(5, -185), |
| 49 | + "Clipping of negative longitude doesn't work."); |
| 50 | + Assert.AreEqual(OpenLocationCode.Encode(5, 175), OpenLocationCode.Encode(5, -905), |
| 51 | + "Clipping of very long negative longitude doesn't work."); |
| 52 | + Assert.AreEqual(OpenLocationCode.Encode(5, -175), OpenLocationCode.Encode(5, 905), |
| 53 | + "Clipping of very long positive longitude doesn't work."); |
| 54 | + } |
| 55 | + |
| 56 | + [Test] |
| 57 | + public void ShouldLimitCodeLengthWhenExceedingMaximum() { |
| 58 | + string code = OpenLocationCode.Encode(51.3701125, -10.202665625, 1000000); |
| 59 | + |
| 60 | + Assert.AreEqual(code.Length, OpenLocationCode.MaxCodeLength + 1, |
| 61 | + "Encoded code should have a length of MaxCodeLength + 1 for the plus symbol"); |
| 62 | + } |
| 63 | + } |
| 64 | + |
| 65 | + public class TheEncodeConstructor { |
| 66 | + [Test] |
| 67 | + public void ShouldEncodePointToExpectedLocationCode() { |
| 68 | + foreach (var testData in TestDataList) { |
| 69 | + OpenLocationCode olc = new OpenLocationCode(testData.EncodedLatitude, testData.EncodedLongitude, testData.CodeDigits.Length); |
| 70 | + Assert.AreEqual(testData.Code, olc.Code, |
| 71 | + $"Wrong code enocded for latitude {testData.EncodedLatitude} and longitude {testData.EncodedLongitude}."); |
| 72 | + } |
| 73 | + } |
| 74 | + [Test] |
| 75 | + public void ShouldTrimCodesIntoToExpectedCodeDigits() { |
| 76 | + foreach (var testData in TestDataList) { |
| 77 | + OpenLocationCode olc = new OpenLocationCode(testData.EncodedLatitude, testData.EncodedLongitude, testData.CodeDigits.Length); |
| 78 | + Assert.AreEqual(testData.CodeDigits, olc.CodeDigits, |
| 79 | + $"Wrong digits trimmed for encoded latitude {testData.EncodedLatitude} and longitude {testData.EncodedLongitude}."); |
| 80 | + } |
| 81 | + } |
| 82 | + } |
| 83 | + |
| 84 | + |
| 85 | + public class TheDecodeMethod { |
| 86 | + [Test] |
| 87 | + public void ShouldDecodeFullCodesToExpectedCodeArea() { |
| 88 | + foreach (var testData in TestDataList) { |
| 89 | + AssertExpectedDecodedArea(testData, OpenLocationCode.Decode(testData.Code)); |
| 90 | + } |
| 91 | + } |
| 92 | + |
| 93 | + [Test] |
| 94 | + public void ShouldDecodeFullCodesWithLowercaseCharactersToExpectedCodeArea() { |
| 95 | + foreach (var testData in TestDataList) { |
| 96 | + AssertExpectedDecodedArea(testData, OpenLocationCode.Decode(testData.Code.ToLower())); |
| 97 | + } |
| 98 | + } |
| 99 | + |
| 100 | + [Test] |
| 101 | + public void ShouldDecodeToCodeAreaWithValidContainmentRelation() { |
| 102 | + foreach (var testData in TestDataList) { |
| 103 | + var olc = new OpenLocationCode(testData.Code); |
| 104 | + var decoded = olc.Decode(); |
| 105 | + Assert.True(decoded.Contains(decoded.CenterLatitude, decoded.CenterLongitude), |
| 106 | + $"Containment relation is broken for the decoded middle point of code {testData.Code}"); |
| 107 | + Assert.True(decoded.Contains(decoded.SouthLatitude, decoded.WestLongitude), |
| 108 | + $"Containment relation is broken for the decoded bottom left corner of code {testData.Code}"); |
| 109 | + Assert.False(decoded.Contains(decoded.NorthLatitude, decoded.EastLongitude), |
| 110 | + $"Containment relation is broken for the decoded top right corner of code {testData.Code}"); |
| 111 | + Assert.False(decoded.Contains(decoded.SouthLatitude, decoded.EastLongitude), |
| 112 | + $"Containment relation is broken for the decoded bottom right corner of code {testData.Code}"); |
| 113 | + Assert.False(decoded.Contains(decoded.NorthLatitude, decoded.WestLongitude), |
| 114 | + $"Containment relation is broken for the decoded top left corner of code {testData.Code}"); |
| 115 | + } |
| 116 | + } |
| 117 | + |
| 118 | + [Test] |
| 119 | + public void ShouldDecodeToCodeAreaWithExpectedDimensions() { |
| 120 | + Assert.AreEqual(20.0, OpenLocationCode.Decode("67000000+").LongitudeWidth, 0); |
| 121 | + Assert.AreEqual(20.0, OpenLocationCode.Decode("67000000+").LatitudeHeight, 0); |
| 122 | + Assert.AreEqual(1.0, OpenLocationCode.Decode("67890000+").LongitudeWidth, 0); |
| 123 | + Assert.AreEqual(1.0, OpenLocationCode.Decode("67890000+").LatitudeHeight, 0); |
| 124 | + Assert.AreEqual(0.05, OpenLocationCode.Decode("6789CF00+").LongitudeWidth, 0); |
| 125 | + Assert.AreEqual(0.05, OpenLocationCode.Decode("6789CF00+").LatitudeHeight, 0); |
| 126 | + Assert.AreEqual(0.0025, OpenLocationCode.Decode("6789CFGH+").LongitudeWidth, 0); |
| 127 | + Assert.AreEqual(0.0025, OpenLocationCode.Decode("6789CFGH+").LatitudeHeight, 0); |
| 128 | + Assert.AreEqual(0.000125, OpenLocationCode.Decode("6789CFGH+JM").LongitudeWidth, 0); |
| 129 | + Assert.AreEqual(0.000125, OpenLocationCode.Decode("6789CFGH+JM").LatitudeHeight, 0); |
| 130 | + Assert.AreEqual(0.00003125, OpenLocationCode.Decode("6789CFGH+JMP").LongitudeWidth, 0); |
| 131 | + Assert.AreEqual(0.000025, OpenLocationCode.Decode("6789CFGH+JMP").LatitudeHeight, 0); |
| 132 | + } |
| 133 | + |
| 134 | + [Test] |
| 135 | + public void ShouldThrowArgumentExceptionForInvalidOrShortCodes() { |
| 136 | + foreach (string code in new[] { null, "INVALID", "9QCJ+2VX" }) { |
| 137 | + Assert.Throws<ArgumentException>(() => OpenLocationCode.Decode(code), |
| 138 | + $"Expected exception was not thrown for code {code}"); |
| 139 | + } |
| 140 | + } |
| 141 | + |
| 142 | + } |
| 143 | + |
| 144 | + public class TheDecodeConstructor { |
| 145 | + [Test] |
| 146 | + public void ShouldAcceptFullCodesAndDecodeToExpectedCodeArea() { |
| 147 | + foreach (var testData in TestDataList) { |
| 148 | + AssertExpectedDecodedArea(testData, new OpenLocationCode(testData.Code).Decode()); |
| 149 | + } |
| 150 | + } |
| 151 | + |
| 152 | + [Test] |
| 153 | + public void ShouldNormalizeFullCodeDigitsToExpectedCode() { |
| 154 | + foreach (var testData in TestDataList) { |
| 155 | + Assert.AreEqual(testData.Code, new OpenLocationCode(testData.CodeDigits).Code, |
| 156 | + $"Wrong code normalized from code digits {testData.CodeDigits}"); |
| 157 | + } |
| 158 | + } |
| 159 | + |
| 160 | + [Test] |
| 161 | + public void ShouldTrimCodesIntoToExpectedCodeDigits() { |
| 162 | + foreach (var testData in TestDataList) { |
| 163 | + Assert.AreEqual(testData.CodeDigits, new OpenLocationCode(testData.Code).CodeDigits, |
| 164 | + $"Wrong digits trimmed from code {testData.Code}."); |
| 165 | + } |
| 166 | + } |
| 167 | + |
| 168 | + [Test] |
| 169 | + public void ShouldThrowArgumentExceptionForInvalidOrShortCodes() { |
| 170 | + foreach (string code in new[] { null, "INVALID", "9QCJ+2VX" }) { |
| 171 | + Assert.Throws<ArgumentException>(() => new OpenLocationCode(code), |
| 172 | + $"Expected exception was not thrown for code {code}"); |
| 173 | + } |
| 174 | + } |
| 175 | + } |
| 176 | + |
| 177 | + private static void AssertExpectedDecodedArea(TestData testData, CodeArea decodedArea) { |
| 178 | + Assert.True(IsNear(testData.DecodedArea.SouthLatitude, decodedArea.SouthLatitude), |
| 179 | + $"Wrong decoded low latitude for code {testData.Code}"); |
| 180 | + Assert.True(IsNear(testData.DecodedArea.NorthLatitude, decodedArea.NorthLatitude), |
| 181 | + $"Wrong decoded high latitude for code {testData.Code}"); |
| 182 | + Assert.True(IsNear(testData.DecodedArea.WestLongitude, decodedArea.WestLongitude), |
| 183 | + $"Wrong decoded low longitude for code {testData.Code}"); |
| 184 | + Assert.True(IsNear(testData.DecodedArea.EastLongitude, decodedArea.EastLongitude), |
| 185 | + $"Wrong decoded high longitude for code {testData.Code}"); |
| 186 | + } |
| 187 | + |
| 188 | + private static bool IsNear(double a, double b) { |
| 189 | + return Math.Abs(a - b) < 1e-10; |
| 190 | + } |
| 191 | + |
| 192 | + |
| 193 | + private struct TestData { |
| 194 | + |
| 195 | + internal TestData(string code, string codeDigits, double lat, double lon, double decodedMinLat, double decodedMinLon, double decodedMaxLat, double decodedMaxLon) { |
| 196 | + Code = code; |
| 197 | + CodeDigits = codeDigits; |
| 198 | + EncodedLatitude = lat; |
| 199 | + EncodedLongitude = lon; |
| 200 | + DecodedArea = new CodeArea(decodedMinLat, decodedMinLon, decodedMaxLat, decodedMaxLon); |
| 201 | + } |
| 202 | + |
| 203 | + internal string Code { get; } |
| 204 | + internal string CodeDigits { get; } |
| 205 | + internal double EncodedLatitude { get; } |
| 206 | + internal double EncodedLongitude { get; } |
| 207 | + internal CodeArea DecodedArea { get; } |
| 208 | + |
| 209 | + } |
| 210 | +} |
0 commit comments