diff --git a/src/Compiler/Input/AbstractSectorDataReader.cs b/src/Compiler/Input/AbstractSectorDataReader.cs index 656eb074..dde435f7 100644 --- a/src/Compiler/Input/AbstractSectorDataReader.cs +++ b/src/Compiler/Input/AbstractSectorDataReader.cs @@ -15,6 +15,17 @@ public bool IsBlankLine(string line) return line.Trim() == ""; } + /* + * Returns whether or not the line is an arc gen line + */ + public bool IsArcGenLine(string line) { + try { + return line.TrimStart().StartsWith("@ARC"); + } catch (ArgumentOutOfRangeException) { + return false; + } + } + /* * Returns whether or not the line is a comment line */ diff --git a/src/Compiler/Input/SectorDataFile.cs b/src/Compiler/Input/SectorDataFile.cs index ca154967..9c8b4933 100644 --- a/src/Compiler/Input/SectorDataFile.cs +++ b/src/Compiler/Input/SectorDataFile.cs @@ -1,5 +1,7 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; +using System.Text.RegularExpressions; using Compiler.Model; namespace Compiler.Input @@ -39,6 +41,118 @@ public override IEnumerator GetEnumerator() { docblock.AddLine(reader.GetCommentSegment(line)); } + else if (reader.IsArcGenLine(line)) { + // Return new SectorData for each new region + + const int DELTA_THETA = 5; + const double R = 6372.795477598; + + Regex rx = new Regex(@"@ARC\(region (.*) centre ([NS]\d{3}\.\d{2}\.\d{2}\.\d{3}) ([EW]\d{3}\.\d{2}\.\d{2}\.\d{3}) radius (\d*(?:\.\d*){0,1})(?: from ([NS]\d{3}\.\d{2}\.\d{2}\.\d{3}) ([EW]\d{3}\.\d{2}\.\d{2}\.\d{3}) to ([NS]\d{3}\.\d{2}\.\d{2}\.\d{3}) ([EW]\d{3}\.\d{2}\.\d{2}\.\d{3})){0,1}\)", RegexOptions.None); + GroupCollection groups = rx.Match(line).Groups; // error catching! + + string regionName = groups[1].Value; + + double lat = Coordinate.DegreeMinSecToDecimalDegree(groups[2].Value); + double lon = Coordinate.DegreeMinSecToDecimalDegree(groups[3].Value); + + float radius = float.Parse(groups[4].Value); + + int initialTheta = 0; + int finalTheta = 360; + + string prevLat = ""; + string prevLon = ""; + + bool includesFromTo = false; + + if (groups.Count > 5) { // includes a from / to as + if (groups[5].Value.Length > 0) { + includesFromTo = true; + prevLat = groups[5].Value; + prevLon = groups[6].Value; + + double fromLat = Coordinate.DegreeMinSecToDecimalDegree(groups[5].Value); + double fromLon = Coordinate.DegreeMinSecToDecimalDegree(groups[6].Value); + + double toLat = Coordinate.DegreeMinSecToDecimalDegree(groups[7].Value); + double toLon = Coordinate.DegreeMinSecToDecimalDegree(groups[8].Value); + + // Calculate angle to from / to coordinate + + double fromTheta = Math.Atan2(Math.Cos(fromLat * Math.PI / 180) * Math.Sin((fromLon - lon) * Math.PI / 180), + Math.Cos(lat * Math.PI / 180) * Math.Sin(fromLat * Math.PI / 180) - Math.Sin(lat * Math.PI / 180) * Math.Cos(fromLat * Math.PI / 180) * Math.Cos((fromLon - lon) * Math.PI / 180) + ); + fromTheta = (180 * fromTheta / Math.PI + 360) % 360; + + double toTheta = Math.Atan2(Math.Cos(toLat * Math.PI / 180) * Math.Sin((toLon - lon) * Math.PI / 180), + Math.Cos(lat * Math.PI / 180) * Math.Sin(toLat * Math.PI / 180) - Math.Sin(lat * Math.PI / 180) * Math.Cos(toLat * Math.PI / 180) * Math.Cos((toLon - lon) * Math.PI / 180) + ); + toTheta = (180 * toTheta / Math.PI + 360) % 360; + + initialTheta = (int)Math.Min(fromTheta, toTheta); + finalTheta = (int)Math.Max(fromTheta, toTheta); + + initialTheta += 1; // padding + finalTheta -= DELTA_THETA; + + // calculate actual radius + + double fromDist = R * Math.Acos(Math.Sin(lat * Math.PI / 180) * Math.Sin(fromLat * Math.PI / 180) + Math.Cos(lat * Math.PI / 180) * Math.Cos(fromLat * Math.PI / 180) * Math.Cos((lon - fromLon) * Math.PI / 180)); + fromDist = fromDist / 1.852; + double toDist = R * Math.Acos(Math.Sin(lat * Math.PI / 180) * Math.Sin(toLat * Math.PI / 180) + Math.Cos(lat * Math.PI / 180) * Math.Cos(toLat * Math.PI / 180) * Math.Cos((lon - toLon) * Math.PI / 180)); + toDist = toDist / 1.852; + + float meanRadius = (float)Math.Round((fromDist + toDist) / 2, 2); + + if (meanRadius != radius) { + radius = meanRadius; // AIP radius was wrong + } + } + } + + for (int theta = initialTheta; theta <= finalTheta; theta += DELTA_THETA) { + double deltaLat = (radius * Math.Cos(theta * Math.PI / 180)) / 60.0d; + double deltaLon = (radius * Math.Sin(theta * Math.PI / 180)) / 60.0d; + deltaLon /= Math.Cos((lat) * Math.PI / 180); // account for length of nautical mile changing with latitude + + string newLat = Coordinate.DecimalDegreeToDegreeMinSec(lat + deltaLat, true); + string newLon = Coordinate.DecimalDegreeToDegreeMinSec(lon + deltaLon, false); + + if (theta == initialTheta && !includesFromTo) { + prevLat = newLat; + prevLon = newLon; + continue; + } + + string outLine = $"{regionName} {prevLat} {prevLon} {newLat} {newLon}"; + + prevLat = newLat; + prevLon = newLon; + + // For each new coordinate, yield return it. + + yield return new SectorData( + docblock, + reader.GetCommentSegment(outLine), + reader.GetDataSegments(outLine), + reader.GetRawData(outLine), + new Definition(this.FullPath, this.CurrentLineNumber) // sus + ); + docblock = new Docblock(); + } + + if (includesFromTo) { + string outLine = $"{regionName} {prevLat} {prevLon} {groups[7].Value} {groups[8].Value}"; + yield return new SectorData( + docblock, + reader.GetCommentSegment(outLine), + reader.GetDataSegments(outLine), + reader.GetRawData(outLine), + new Definition(this.FullPath, this.CurrentLineNumber) // sus + ); + docblock = new Docblock(); + } + } else { yield return new SectorData( diff --git a/src/Compiler/Model/Coordinate.cs b/src/Compiler/Model/Coordinate.cs index 1b8e0289..04bc31ad 100644 --- a/src/Compiler/Model/Coordinate.cs +++ b/src/Compiler/Model/Coordinate.cs @@ -1,4 +1,6 @@ -namespace Compiler.Model +using System; + +namespace Compiler.Model { public struct Coordinate { @@ -11,6 +13,45 @@ public Coordinate(string latitude, string longitude) this.longitude = longitude; } + public static double DegreeMinSecToDecimalDegree(string latOrLong) { + double output = 0; + string[] sections = latOrLong.Split('.'); + output += int.Parse(sections[0].Substring(1)); + output += int.Parse(sections[1]) / 60.0d; + output += int.Parse(sections[2]) / 3600.0d; + output += int.Parse(sections[3]) / 3600000.0d; + if (sections[0].StartsWith("S") || sections[0].StartsWith("W")) { + output = -output; + } + return output; + } + + public static string DecimalDegreeToDegreeMinSec(double decimalDegree, bool isLat) { + string output = ""; + if (decimalDegree > 0) { + if (isLat) output += "N"; + else output += "E"; + } else { + decimalDegree = -decimalDegree; + if (isLat) output += "S"; + else output += "W"; + } + + output += ((int)decimalDegree).ToString().PadLeft(3, '0') + "."; + decimalDegree -= (int)decimalDegree; + decimalDegree *= 60; + output += ((int)decimalDegree).ToString().PadLeft(2, '0') + "."; + decimalDegree -= (int)decimalDegree; + decimalDegree *= 60; + output += ((int)decimalDegree).ToString().PadLeft(2, '0') + "."; + decimalDegree -= (int)decimalDegree; + decimalDegree *= 1000; + if (Math.Round(decimalDegree) > 999) output += "999"; + else output += Math.Round(decimalDegree).ToString().PadLeft(3, '0'); + + return output; + } + public override bool Equals(object obj) { return (obj is Coordinate) && diff --git a/tests/CompilerTest/CompilerTest.csproj b/tests/CompilerTest/CompilerTest.csproj index 50449bd8..d68ad70b 100644 --- a/tests/CompilerTest/CompilerTest.csproj +++ b/tests/CompilerTest/CompilerTest.csproj @@ -47,6 +47,7 @@ + diff --git a/tests/CompilerTest/Input/EseSectorDataReaderTest.cs b/tests/CompilerTest/Input/EseSectorDataReaderTest.cs index e67df2c3..05d0d4d5 100644 --- a/tests/CompilerTest/Input/EseSectorDataReaderTest.cs +++ b/tests/CompilerTest/Input/EseSectorDataReaderTest.cs @@ -43,11 +43,22 @@ public void ItDetectsABlankLine(string line, bool expected) [InlineData("", false)] [InlineData("// comment", false)] [InlineData("/* comment */", false)] + [InlineData("@ARC", false)] public void ItDetectsACommentLine(string line, bool expected) { Assert.Equal(expected, this.reader.IsCommentLine(line)); } + [Fact] + public void ItIgnoresShortComments() { + Assert.False(this.reader.IsArcGenLine("@AR")); + } + + [Fact] + public void ItRecognisesArcGenLines() { + Assert.True(this.reader.IsArcGenLine("@ARC(xxx)")); + } + [Theory] [InlineData("abc ;", "")] [InlineData("abc ;comment", "comment")] diff --git a/tests/CompilerTest/Input/SctSectorDataReaderTest.cs b/tests/CompilerTest/Input/SctSectorDataReaderTest.cs index 6cf97a58..8544303d 100644 --- a/tests/CompilerTest/Input/SctSectorDataReaderTest.cs +++ b/tests/CompilerTest/Input/SctSectorDataReaderTest.cs @@ -43,11 +43,21 @@ public void ItDetectsABlankLine(string line, bool expected) [InlineData("", false)] [InlineData("// comment", false)] [InlineData("/* comment */", false)] - public void ItDetectsACommentLine(string line, bool expected) - { + [InlineData("@ARC", false)] + public void ItDetectsACommentLine(string line, bool expected) { Assert.Equal(expected, this.reader.IsCommentLine(line)); } + [Fact] + public void ItIgnoresShortComments() { + Assert.False(this.reader.IsArcGenLine("@AR")); + } + + [Fact] + public void ItRecognisesArcGenLines() { + Assert.True(this.reader.IsArcGenLine("@ARC(xxx)")); + } + [Theory] [InlineData("abc ;", "")] [InlineData("abc ;comment", "comment")] diff --git a/tests/CompilerTest/Input/SectorDataFileTest.cs b/tests/CompilerTest/Input/SectorDataFileTest.cs index 3ca9fdcb..47bcf019 100644 --- a/tests/CompilerTest/Input/SectorDataFileTest.cs +++ b/tests/CompilerTest/Input/SectorDataFileTest.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Compiler.Input; using Compiler.Model; using Xunit; @@ -8,6 +9,7 @@ namespace CompilerTest.Input public class SectorDataFileTest { private readonly SectorDataFile file; + private readonly SectorDataFile arcGenFile; public SectorDataFileTest() { @@ -17,6 +19,13 @@ public SectorDataFileTest() InputDataType.ESE_AGREEMENTS, new EseSectorDataReader() ); + + arcGenFile = new SectorDataFile( + "_TestData/SectorDataFile/ArcGenTest.txt", + new InputFileStreamFactory(), + InputDataType.ESE_AGREEMENTS, + new EseSectorDataReader() + ); } [Fact] @@ -85,6 +94,21 @@ public void TestItIteratesTheInputFile() Assert.Equal(33, file.CurrentLineNumber); } + + [Fact] + public void TestItIteratesArcGen() { + string[] lines = { "Test Region N051.31.15.000 W000.07.30.000 N051.31.14.715 W000.07.19.500", "Test Region N051.31.14.715 W000.07.19.500 N051.31.13.861 W000.07.09.079", "Test Region N051.31.13.861 W000.07.09.079 N051.31.12.444 W000.06.58.818", "Test Region N051.31.12.444 W000.06.58.818 N051.31.10.477 W000.06.48.794", "Test Region N051.31.10.477 W000.06.48.794 N051.31.07.973 W000.06.39.083", "Test Region N051.31.07.973 W000.06.39.083 N051.31.04.952 W000.06.29.760", "Test Region N051.31.04.952 W000.06.29.760 N051.31.01.436 W000.06.20.896", "Test Region N051.31.01.436 W000.06.20.896 N051.30.57.453 W000.06.12.558", "Test Region N051.30.57.453 W000.06.12.558 N051.30.53.033 W000.06.04.808", "Test Region N051.30.53.033 W000.06.04.808 N051.30.48.209 W000.05.57.708", "Test Region N051.30.48.209 W000.05.57.708 N051.30.43.018 W000.05.51.309", "Test Region N051.30.43.018 W000.05.51.309 N051.30.37.500 W000.05.45.662", "Test Region N051.30.37.500 W000.05.45.662 N051.30.31.696 W000.05.40.809", "Test Region N051.30.31.696 W000.05.40.809 N051.30.25.652 W000.05.36.787", "Test Region N051.30.25.652 W000.05.36.787 N051.30.19.411 W000.05.33.626", "Test Region N051.30.19.411 W000.05.33.626 N051.30.13.024 W000.05.31.351", "Test Region N051.30.13.024 W000.05.31.351 N051.30.06.537 W000.05.29.979", "Test Region N051.30.06.537 W000.05.29.979 N051.30.00.000 W000.05.29.521", "Test Region N051.30.00.000 W000.05.29.521 N051.29.53.463 W000.05.29.979", "Test Region N051.29.53.463 W000.05.29.979 N051.29.46.976 W000.05.31.351", "Test Region N051.29.46.976 W000.05.31.351 N051.29.40.589 W000.05.33.626", "Test Region N051.29.40.589 W000.05.33.626 N051.29.34.348 W000.05.36.787", "Test Region N051.29.34.348 W000.05.36.787 N051.29.28.304 W000.05.40.809", "Test Region N051.29.28.304 W000.05.40.809 N051.29.22.500 W000.05.45.662", "Test Region N051.29.22.500 W000.05.45.662 N051.29.16.982 W000.05.51.309", "Test Region N051.29.16.982 W000.05.51.309 N051.29.11.791 W000.05.57.708", "Test Region N051.29.11.791 W000.05.57.708 N051.29.06.967 W000.06.04.808", "Test Region N051.29.06.967 W000.06.04.808 N051.29.02.547 W000.06.12.558", "Test Region N051.29.02.547 W000.06.12.558 N051.28.58.564 W000.06.20.896", "Test Region N051.28.58.564 W000.06.20.896 N051.28.55.048 W000.06.29.760", "Test Region N051.28.55.048 W000.06.29.760 N051.28.52.027 W000.06.39.083", "Test Region N051.28.52.027 W000.06.39.083 N051.28.49.523 W000.06.48.794", "Test Region N051.28.49.523 W000.06.48.794 N051.28.47.556 W000.06.58.818", "Test Region N051.28.47.556 W000.06.58.818 N051.28.46.139 W000.07.09.079", "Test Region N051.28.46.139 W000.07.09.079 N051.28.45.285 W000.07.19.500", "Test Region N051.28.45.285 W000.07.19.500 N051.28.44.999 W000.07.30.000", "Test Region N051.28.44.999 W000.07.30.000 N051.28.45.285 W000.07.40.500", "Test Region N051.28.45.285 W000.07.40.500 N051.28.46.139 W000.07.50.921", "Test Region N051.28.46.139 W000.07.50.921 N051.28.47.556 W000.08.01.182", "Test Region N051.28.47.556 W000.08.01.182 N051.28.49.523 W000.08.11.206", "Test Region N051.28.49.523 W000.08.11.206 N051.28.52.027 W000.08.20.917", "Test Region N051.28.52.027 W000.08.20.917 N051.28.55.048 W000.08.30.240", "Test Region N051.28.55.048 W000.08.30.240 N051.28.58.564 W000.08.39.104", "Test Region N051.28.58.564 W000.08.39.104 N051.29.02.547 W000.08.47.442", "Test Region N051.29.02.547 W000.08.47.442 N051.29.06.967 W000.08.55.192", "Test Region N051.29.06.967 W000.08.55.192 N051.29.11.791 W000.09.02.292", "Test Region N051.29.11.791 W000.09.02.292 N051.29.16.982 W000.09.08.691", "Test Region N051.29.16.982 W000.09.08.691 N051.29.22.500 W000.09.14.338", "Test Region N051.29.22.500 W000.09.14.338 N051.29.28.304 W000.09.19.191", "Test Region N051.29.28.304 W000.09.19.191 N051.29.34.348 W000.09.23.213", "Test Region N051.29.34.348 W000.09.23.213 N051.29.40.589 W000.09.26.374", "Test Region N051.29.40.589 W000.09.26.374 N051.29.46.976 W000.09.28.649", "Test Region N051.29.46.976 W000.09.28.649 N051.29.53.463 W000.09.30.021", "Test Region N051.29.53.463 W000.09.30.021 N051.30.00.000 W000.09.30.479", "Test Region N051.30.00.000 W000.09.30.479 N051.30.06.537 W000.09.30.021", "Test Region N051.30.06.537 W000.09.30.021 N051.30.13.024 W000.09.28.649", "Test Region N051.30.13.024 W000.09.28.649 N051.30.19.411 W000.09.26.374", "Test Region N051.30.19.411 W000.09.26.374 N051.30.25.652 W000.09.23.213", "Test Region N051.30.25.652 W000.09.23.213 N051.30.31.696 W000.09.19.191", "Test Region N051.30.31.696 W000.09.19.191 N051.30.37.500 W000.09.14.338", "Test Region N051.30.37.500 W000.09.14.338 N051.30.43.018 W000.09.08.691", "Test Region N051.30.43.018 W000.09.08.691 N051.30.48.209 W000.09.02.292", "Test Region N051.30.48.209 W000.09.02.292 N051.30.53.033 W000.08.55.192", "Test Region N051.30.53.033 W000.08.55.192 N051.30.57.453 W000.08.47.442", "Test Region N051.30.57.453 W000.08.47.442 N051.31.01.436 W000.08.39.104", "Test Region N051.31.01.436 W000.08.39.104 N051.31.04.952 W000.08.30.240", "Test Region N051.31.04.952 W000.08.30.240 N051.31.07.973 W000.08.20.917", "Test Region N051.31.07.973 W000.08.20.917 N051.31.10.477 W000.08.11.206", "Test Region N051.31.10.477 W000.08.11.206 N051.31.12.444 W000.08.01.182", "Test Region N051.31.12.444 W000.08.01.182 N051.31.13.861 W000.07.50.921", "Test Region N051.31.13.861 W000.07.50.921 N051.31.14.715 W000.07.40.500", "Test Region N051.31.14.715 W000.07.40.500 N051.31.15.000 W000.07.30.000" }; + string[] lines2 = { "Test Region N051.29.17.000 W000.06.34.000 N051.29.16.696 W000.06.34.039", "Test Region N051.29.16.696 W000.06.34.039 N051.29.13.869 W000.06.33.999", "Test Region N051.29.13.869 W000.06.33.999 N051.29.11.051 W000.06.34.355", "Test Region N051.29.11.051 W000.06.34.355 N051.29.08.264 W000.06.35.104", "Test Region N051.29.08.264 W000.06.35.104 N051.29.05.527 W000.06.36.241", "Test Region N051.29.05.527 W000.06.36.241 N051.29.02.863 W000.06.37.756", "Test Region N051.29.02.863 W000.06.37.756 N051.29.00.291 W000.06.39.639", "Test Region N051.29.00.291 W000.06.39.639 N051.28.57.831 W000.06.41.874", "Test Region N051.28.57.831 W000.06.41.874 N051.28.55.501 W000.06.44.445", "Test Region N051.28.55.501 W000.06.44.445 N051.28.52.000 W000.06.49.000" }; + int i = 0; + foreach (SectorData dataLine in arcGenFile) { + if (i < lines.Length) { + Assert.Equal(lines[i], dataLine.rawData); + } else { + Assert.Equal(lines2[i - lines.Length], dataLine.rawData); + } + i += 1; + } + } [Fact] public void ItsEqualIfPathTheSame() diff --git a/tests/CompilerTest/Model/CoordinateTest.cs b/tests/CompilerTest/Model/CoordinateTest.cs index c818e70a..a105d530 100644 --- a/tests/CompilerTest/Model/CoordinateTest.cs +++ b/tests/CompilerTest/Model/CoordinateTest.cs @@ -29,5 +29,25 @@ public void TestItRepresentsAsString() { Assert.Equal("abc def", coordinate.ToString()); } + + [Theory] + [InlineData(54.51555556, "N054.30.56.000")] + [InlineData(51.26757306, "N051.16.03.263")] + [InlineData(-23.85944194, "S023.51.33.991")] + [InlineData(0.52033000, "E000.31.13.188")] + [InlineData(-3.19839500, "W003.11.54.222")] + public void TestDegreeMinSecToDecimalDegree(double expected, string coordinateString) { + Assert.Equal(expected, Coordinate.DegreeMinSecToDecimalDegree(coordinateString), 0.00000001); // tolerance for float precision + } + + [Theory] + [InlineData(54.51555556, "N054.30.56.000", true)] + [InlineData(51.26757306, "N051.16.03.263", true)] + [InlineData(-23.85944194, "S023.51.33.991", true)] + [InlineData(0.52033000, "E000.31.13.188", false)] + [InlineData(-3.19839500, "W003.11.54.222", false)] + public void TestDecimalDegreeToDegreeMinSec(double coordinate, string expected, bool isLat) { + Assert.Equal(expected, Coordinate.DecimalDegreeToDegreeMinSec(coordinate, isLat)); + } } } diff --git a/tests/CompilerTest/_TestData/SectorDataFile/ArcGenTest.txt b/tests/CompilerTest/_TestData/SectorDataFile/ArcGenTest.txt new file mode 100644 index 00000000..61e4af70 --- /dev/null +++ b/tests/CompilerTest/_TestData/SectorDataFile/ArcGenTest.txt @@ -0,0 +1,4 @@ +;Simple +@ARC(region Test Region centre N051.30.00.000 W000.07.30.000 radius 1.25) +;Complex +@ARC(region Test Region centre N051.29.15.000 W000.07.26.000 radius 0.55 from N051.29.17.000 W000.06.34.000 to N051.28.52.000 W000.06.49.000) \ No newline at end of file