diff --git a/ProjNet.IO.Wkt.Tests/Core/WktParserTests.cs b/ProjNet.IO.Wkt.Tests/Core/WktParserTests.cs new file mode 100644 index 0000000..41a1dc2 --- /dev/null +++ b/ProjNet.IO.Wkt.Tests/Core/WktParserTests.cs @@ -0,0 +1,124 @@ +using NUnit.Framework; +using Pidgin; +using ProjNet.IO.Wkt.Core; + +namespace ProjNet.IO.Wkt.Tests.Core; + +public class WktParserTests +{ + + [Test] + public void TestUnsignedIntegerParser() + { + // Arrange + string parserText01 = $@"210677"; + + // Act + uint parserResult01 = WktParser.UnsignedIntegerParser.ParseOrThrow(parserText01); + + // Assert + Assert.That(parserResult01, Is.EqualTo(210677)); + } + + [Test] + public void TestSignedIntegerParser() + { + // Arrange + string parserText01 = $@"+210677"; + string parserText02 = $@"-210677"; + string parserText03 = $@"210677"; + + // Act + int parserResult01 = WktParser.SignedIntegerParser.ParseOrThrow(parserText01); + int parserResult02 = WktParser.SignedIntegerParser.ParseOrThrow(parserText02); + int parserResult03 = WktParser.SignedIntegerParser.ParseOrThrow(parserText03); + + // Assert + Assert.That(parserResult01, Is.EqualTo(210677)); + Assert.That(parserResult02, Is.EqualTo(-210677)); + Assert.That(parserResult03, Is.EqualTo(210677)); + } + + + [Test] + public void TestSignedNumericLiteralParser() + { + // Arrange + string parserText01 = "-100.333333333333"; + string parserText02 = "+100.333333333333"; + string parserText03 = "100.333333333333"; + string parserText04 = ".333333333333"; + + // Act + double parserResult01 = WktParser.SignedNumericLiteralParser.ParseOrThrow(parserText01); + double parserResult02 = WktParser.SignedNumericLiteralParser.ParseOrThrow(parserText02); + double parserResult03 = WktParser.SignedNumericLiteralParser.ParseOrThrow(parserText03); + double parserResult04 = WktParser.SignedNumericLiteralParser.ParseOrThrow(parserText04); + + // Assert + Assert.That(parserResult01, Is.EqualTo(-100.333333333333)); + Assert.That(parserResult02, Is.EqualTo(100.333333333333)); + Assert.That(parserResult03, Is.EqualTo(100.333333333333)); + Assert.That(parserResult04, Is.EqualTo(0.333333333333)); + } + + [Test] + public void TestExactNumericLiteralParser() + { + // Arrange + string parserText01 = $@"21.043"; + string parserText02 = $@"0.043"; + string parserText03 = $@".043"; + + // Act + double parserResult01 = WktParser.ExactNumericLiteralParser.ParseOrThrow(parserText01); + double parserResult02 = WktParser.ExactNumericLiteralParser.ParseOrThrow(parserText02); + double parserResult03 = WktParser.ExactNumericLiteralParser.ParseOrThrow(parserText03); + + // Assert + Assert.That(parserResult01, Is.EqualTo(21.043d)); + Assert.That(parserResult02, Is.EqualTo(0.043d)); + Assert.That(parserResult03, Is.EqualTo(0.043d)); + } + + [Test] + public void TestApproximateNumericLiteralParser() + { + // Arrange + string parserText01 = $@"21.04E-3"; + string parserText02= $@"21.04E+3"; + string parserText03 = $@"21.04E3"; + string parserText04 = $@"0.04E3"; + string parserText05 = $@".04E3"; + + // Act + double parserResult01 = WktParser.ApproximateNumericLiteralParser.ParseOrThrow(parserText01); + double parserResult02 = WktParser.ApproximateNumericLiteralParser.ParseOrThrow(parserText02); + double parserResult03 = WktParser.ApproximateNumericLiteralParser.ParseOrThrow(parserText03); + double parserResult04 = WktParser.ApproximateNumericLiteralParser.ParseOrThrow(parserText04); + double parserResult05 = WktParser.ApproximateNumericLiteralParser.ParseOrThrow(parserText05); + + // Assert + Assert.That(parserResult01, Is.EqualTo(0.02104d)); + Assert.That(parserResult02, Is.EqualTo(21040d)); + Assert.That(parserResult03, Is.EqualTo(21040d)); + Assert.That(parserResult04, Is.EqualTo(40d)); + Assert.That(parserResult05, Is.EqualTo(40d)); + } + + [Test] + public void TestQuotedNameParser() + { + // Arrange + string str01 = "MyTextString"; + string parserText01 = $"\"{str01}\""; + + // Act + string parserResult01 = WktParser.QuotedNameParser.ParseOrThrow(parserText01); + + // Assert + Assert.That(parserResult01, Is.EqualTo(str01)); + } + + +} diff --git a/ProjNet4GeoAPI.sln b/ProjNet4GeoAPI.sln index 1da9334..0da5ecc 100644 --- a/ProjNet4GeoAPI.sln +++ b/ProjNet4GeoAPI.sln @@ -15,6 +15,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProjNET.Tests", "test\ProjN EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProjNet.Benchmark", "src\ProjNet.Benchmark\ProjNet.Benchmark.csproj", "{1E5D1CC5-8CFE-4C9D-9553-900469C57D6C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjNet.IO.Wkt", "src\ProjNet.IO.Wkt\ProjNet.IO.Wkt.csproj", "{50AAB59B-25FB-4E36-A31A-B22C62B2105E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjNet.IO.Wkt.Tests", "ProjNet.IO.Wkt.Tests\ProjNet.IO.Wkt.Tests.csproj", "{3E23D15C-56F1-4431-B241-FC17D2F54BBF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -33,6 +37,14 @@ Global {1E5D1CC5-8CFE-4C9D-9553-900469C57D6C}.Debug|Any CPU.Build.0 = Debug|Any CPU {1E5D1CC5-8CFE-4C9D-9553-900469C57D6C}.Release|Any CPU.ActiveCfg = Release|Any CPU {1E5D1CC5-8CFE-4C9D-9553-900469C57D6C}.Release|Any CPU.Build.0 = Release|Any CPU + {50AAB59B-25FB-4E36-A31A-B22C62B2105E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {50AAB59B-25FB-4E36-A31A-B22C62B2105E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50AAB59B-25FB-4E36-A31A-B22C62B2105E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {50AAB59B-25FB-4E36-A31A-B22C62B2105E}.Release|Any CPU.Build.0 = Release|Any CPU + {3E23D15C-56F1-4431-B241-FC17D2F54BBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3E23D15C-56F1-4431-B241-FC17D2F54BBF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3E23D15C-56F1-4431-B241-FC17D2F54BBF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3E23D15C-56F1-4431-B241-FC17D2F54BBF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/ProjNet.IO.Wkt/AssemblyInfo.cs b/src/ProjNet.IO.Wkt/AssemblyInfo.cs new file mode 100644 index 0000000..9e13281 --- /dev/null +++ b/src/ProjNet.IO.Wkt/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly:InternalsVisibleTo("ProjNet.IO.Wkt.Tests")] diff --git a/src/ProjNet.IO.Wkt/Core/DefaultWktOutputFormatter.cs b/src/ProjNet.IO.Wkt/Core/DefaultWktOutputFormatter.cs new file mode 100644 index 0000000..91d1dba --- /dev/null +++ b/src/ProjNet.IO.Wkt/Core/DefaultWktOutputFormatter.cs @@ -0,0 +1,198 @@ +using System; +using System.Globalization; +using System.Text; + +namespace ProjNet.IO.Wkt.Core +{ + /// <summary> + /// DefaultWktOutputFormatter - Keeping output compact with original delimiters. + /// </summary> + public class DefaultWktOutputFormatter : IWktOutputFormatter + { + private int indentCounter = 0; + + /// <inheritdoc/> + public string Newline { get; } = null; + /// <inheritdoc/> + public char? LeftDelimiter { get; } = null; + /// <inheritdoc/> + public char? RightDelimiter { get; } = null; + + /// <inheritdoc/> + public string Separator { get; } = null; + + /// <summary> + /// Indent chars. E.g. tab or spaces. Default null. + /// </summary> + public string Indent { get; } = null; + + /// <inheritdoc/> + public string ExtraWhitespace { get; } = null; + + + /// <summary> + /// Constructor with support for optional overriding the settings. + /// </summary> + /// <param name="newline"></param> + /// <param name="leftDelimiter"></param> + /// <param name="rightDelimiter"></param> + /// <param name="indent"></param> + /// <param name="extraWhitespace"></param> + public DefaultWktOutputFormatter( + string newline = null, + char? leftDelimiter = null, + char? rightDelimiter = null, + string indent = null, + string extraWhitespace = null) + { + Newline = newline; + LeftDelimiter = leftDelimiter; + RightDelimiter = rightDelimiter; + Indent = indent; + ExtraWhitespace = extraWhitespace; + } + + + /// <inheritdoc/> + public IWktOutputFormatter AppendKeyword(string text, StringBuilder result, bool indent = true) + { + if (indent) + this.AppendIndent(result); + + result.Append(text); + + this.IncreaseIndentCounter(); + + return this; + } + + /// <inheritdoc/> + public IWktOutputFormatter AppendSeparator(StringBuilder result, bool keepInside = false) + { + string s = Separator ?? ","; + result.Append(s); + if (!keepInside) + { + this.AppendNewline(result); + } + + return this; + } + + + /// <inheritdoc/> + public IWktOutputFormatter Append(string text, StringBuilder result) + { + result.Append(text); + return this; + } + + /// <inheritdoc/> + public IWktOutputFormatter Append(long l, StringBuilder result) + { + result.Append(l); + return this; + } + + /// <inheritdoc/> + public IWktOutputFormatter Append(double d, StringBuilder result) + { + result.Append(d.ToString(CultureInfo.InvariantCulture)); + return this; + } + + /// <inheritdoc/> + public IWktOutputFormatter AppendNewline(StringBuilder result) + { + if (!string.IsNullOrEmpty(Newline)) + { + result.Append(Newline); + } + + return this; + } + + /// <inheritdoc/> + public IWktOutputFormatter AppendQuotedText(string text, StringBuilder result) + { + result.Append($"\"{text}\""); + return this; + } + + /// <inheritdoc/> + public IWktOutputFormatter AppendLeftDelimiter(char original, StringBuilder result) + { + if (LeftDelimiter != null) + result.Append(LeftDelimiter); + else + result.Append(original); + return this; + } + + /// <inheritdoc/> + public IWktOutputFormatter AppendRightDelimiter(char original, StringBuilder result) + { + //this.AppendIndent(result); + + if (RightDelimiter != null) + result.Append(RightDelimiter); + else + result.Append(original); + + this.DecreaseIndentCounter(); + //this.AppendNewline(result); + + return this; + } + + /// <inheritdoc/> + public IWktOutputFormatter AppendExtraWhitespace(StringBuilder result) + { + if (!string.IsNullOrEmpty(ExtraWhitespace)) + { + result.Append(ExtraWhitespace); + } + + return this; + } + + /// <summary> + /// Increasing the indentCounter. + /// </summary> + /// <returns></returns> + public IWktOutputFormatter IncreaseIndentCounter() + { + indentCounter++; + return this; + } + + /// <summary> + /// Decreasing the indentCounter. + /// </summary> + /// <returns></returns> + public IWktOutputFormatter DecreaseIndentCounter() + { + indentCounter--; + return this; + } + + + /// <summary> + /// AppendIndent repeat Indent according to internal indentCounter. + /// </summary> + /// <param name="result"></param> + /// <returns></returns> + public IWktOutputFormatter AppendIndent(StringBuilder result) + { + if (!string.IsNullOrEmpty(Indent)) + { + for (int i = 1; i <= indentCounter; i++) + { + result.Append(Indent); + } + } + + return this; + } + } +} diff --git a/src/ProjNet.IO.Wkt/Core/IWktBuilder.cs b/src/ProjNet.IO.Wkt/Core/IWktBuilder.cs new file mode 100644 index 0000000..34723b6 --- /dev/null +++ b/src/ProjNet.IO.Wkt/Core/IWktBuilder.cs @@ -0,0 +1,435 @@ +using System.Collections.Generic; +using Pidgin; + +namespace ProjNet.IO.Wkt.Core +{ + /// <summary> + /// Interface for building/creating all Wkt related objects. + /// </summary> + public interface IWktBuilder + { + /// <summary> + /// Building the Authority object. + /// </summary> + /// <param name="offset"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="name"></param> + /// <param name="code"></param> + /// <param name="rightDelimiter"></param> + /// <returns></returns> + object BuildAuthority( + int offset, + string keyword, + char leftDelimiter, + string name, + int code, + char rightDelimiter + ); + + /// <summary> + /// Build the Axis object. + /// </summary> + /// <param name="offset"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="name"></param> + /// <param name="direction"></param> + /// <param name="rightDelimiter"></param> + /// <returns></returns> + object BuildAxis( + int offset, + string keyword, + char leftDelimiter, + string name, + string direction, + char rightDelimiter); + + + /// <summary> + /// Build the Extension object. + /// </summary> + /// <param name="offset"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="name"></param> + /// <param name="direction"></param> + /// <param name="rightDelimiter"></param> + /// <returns></returns> + object BuildExtension( + int offset, + string keyword, + char leftDelimiter, + string name, + string direction, + char rightDelimiter); + + + /// <summary> + /// Build the ToWgs84 object. + /// </summary> + /// <param name="offset"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="dxShift"></param> + /// <param name="dyShift"></param> + /// <param name="dzShift"></param> + /// <param name="exRotation"></param> + /// <param name="eyRotation"></param> + /// <param name="ezRotation"></param> + /// <param name="ppmScaling"></param> + /// <param name="description"></param> + /// <param name="rightDelimiter"></param> + /// <returns></returns> + object BuildToWgs84( + int offset, + string keyword, + char leftDelimiter, + double dxShift, + double dyShift, + double dzShift, + double exRotation, + double eyRotation, + double ezRotation, + double ppmScaling, + string description, + char rightDelimiter); + + + /// <summary> + /// Build the Projection object. + /// </summary> + /// <param name="offset"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="name"></param> + /// <param name="authority"></param> + /// <param name="rightDelimiter"></param> + /// <returns></returns> + object BuildProjection( + int offset, + string keyword, + char leftDelimiter, + string name, + object authority, + char rightDelimiter); + + + + /// <summary> + /// Build the (Projection) Parameter. + /// </summary> + /// <param name="offset"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="name"></param> + /// <param name="value"></param> + /// <param name="rightDelimiter"></param> + /// <returns></returns> + object BuildProjectionParameter( + int offset, + string keyword, + char leftDelimiter, + string name, + double value, + char rightDelimiter); + + /// <summary> + /// Build the Ellipsoid object. + /// </summary> + /// <param name="offset"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="name"></param> + /// <param name="semiMajorAxis"></param> + /// <param name="inverseFlattening"></param> + /// <param name="authority"></param> + /// <param name="rightDelimiter"></param> + /// <returns></returns> + object BuildEllipsoid( + int offset, + string keyword, + char leftDelimiter, + string name, + double semiMajorAxis, + double inverseFlattening, + object authority, + char rightDelimiter); + + + /// <summary> + /// Build the Spheroid object. + /// </summary> + /// <param name="offset"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="name"></param> + /// <param name="semiMajorAxis"></param> + /// <param name="inverseFlattening"></param> + /// <param name="authority"></param> + /// <param name="rightDelimiter"></param> + /// <returns></returns> + object BuildSpheroid( + int offset, + string keyword, + char leftDelimiter, + string name, + double semiMajorAxis, + double inverseFlattening, + object authority, + char rightDelimiter); + + + /// <summary> + /// Build the primemeridian object. + /// </summary> + /// <param name="offset"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="name"></param> + /// <param name="longitude"></param> + /// <param name="authority"></param> + /// <param name="rightDelimiter"></param> + /// <returns></returns> + object BuildPrimeMeridian( + int offset, + string keyword, + char leftDelimiter, + string name, + double longitude, + object authority, + char rightDelimiter); + + + /// <summary> + /// Build a Unit object. + /// </summary> + /// <param name="offset"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="name"></param> + /// <param name="factor"></param> + /// <param name="authority"></param> + /// <param name="rightDelimiter"></param> + /// <returns></returns> + object BuildUnit( + int offset, + string keyword, + char leftDelimiter, + string name, + double factor, + object authority, + char rightDelimiter); + + + /// <summary> + /// Build a LinearUnit object. + /// </summary> + /// <param name="offset"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="name"></param> + /// <param name="factor"></param> + /// <param name="authority"></param> + /// <param name="rightDelimiter"></param> + /// <returns></returns> + object BuildLinearUnit( + int offset, + string keyword, + char leftDelimiter, + string name, + double factor, + object authority, + char rightDelimiter); + + + /// <summary> + /// Build an AngularUnit object. + /// </summary> + /// <param name="offset"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="name"></param> + /// <param name="factor"></param> + /// <param name="authority"></param> + /// <param name="rightDelimiter"></param> + /// <returns></returns> + object BuildAngularUnit( + int offset, + string keyword, + char leftDelimiter, + string name, + double factor, + object authority, + char rightDelimiter); + + + /// <summary> + /// Build the Datum object. + /// </summary> + /// <param name="offset"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="name"></param> + /// <param name="spheroid"></param> + /// <param name="toWgs84"></param> + /// <param name="authority"></param> + /// <param name="rightDelimiter"></param> + /// <returns></returns> + object BuildDatum( + int offset, + string keyword, + char leftDelimiter, + string name, + object spheroid, + object toWgs84, + object authority, + char rightDelimiter); + + + /// <summary> + /// Build the GeographicCoordinateSystem object. + /// </summary> + /// <param name="offset"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="name"></param> + /// <param name="datum"></param> + /// <param name="meridian"></param> + /// <param name="unit"></param> + /// <param name="axes"></param> + /// <param name="authority"></param> + /// <param name="rightDelimiter"></param> + /// <returns></returns> + object BuildGeographicCoordinateSystem( + int offset, + string keyword, + char leftDelimiter, + string name, + object datum, + object meridian, + object unit, + IEnumerable<object> axes, + object authority, + char rightDelimiter); + + + /// <summary> + /// Build a GeocentricCoordinateSystem object. + /// </summary> + /// <param name="offset"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="name"></param> + /// <param name="datum"></param> + /// <param name="meridian"></param> + /// <param name="unit"></param> + /// <param name="axes"></param> + /// <param name="authority"></param> + /// <param name="rightDelimiter"></param> + /// <returns></returns> + object BuildGeocentricCoordinateSystem( + int offset, + string keyword, + char leftDelimiter, + string name, + object datum, + object meridian, + object unit, + IEnumerable<object> axes, + object authority, + char rightDelimiter); + + + /// <summary> + /// Build the ProjectedCoordiniateSystem object. + /// </summary> + /// <param name="offset"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="name"></param> + /// <param name="geogcs"></param> + /// <param name="projection"></param> + /// <param name="parameters"></param> + /// <param name="unit"></param> + /// <param name="axes"></param> + /// <param name="extension"></param> + /// <param name="authority"></param> + /// <param name="rightDelimiter"></param> + /// <returns></returns> + object BuildProjectedCoordinateSystem( + int offset, + string keyword, + char leftDelimiter, + string name, + object geogcs, + object projection, + object parameters, + object unit, + IEnumerable<object> axes, + object extension, + object authority, + char rightDelimiter); + + + + /// <summary> + /// Build the MathTransform Parameter. + /// </summary> + /// <param name="offset"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="name"></param> + /// <param name="value"></param> + /// <param name="rightDelimiter"></param> + /// <returns></returns> + object BuildParameter( + int offset, + string keyword, + char leftDelimiter, + string name, + double value, + char rightDelimiter); + + /// <summary> + /// Build a ParameterMathTransform object. (Part of FittedCoordinateSystem). + /// </summary> + /// <param name="offset"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="name"></param> + /// <param name="parameters"></param> + /// <param name="rightDelimiter"></param> + /// <returns></returns> + object BuildParameterMathTransform( + int offset, + string keyword, + char leftDelimiter, + string name, + object parameters, + char rightDelimiter); + + + /// <summary> + /// Build the FittedCoordinateSystem object. + /// </summary> + /// <param name="offset"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="name"></param> + /// <param name="pmt"></param> + /// <param name="projcs"></param> + /// <param name="authority"></param> + /// <param name="rightDelimiter"></param> + /// <returns></returns> + object BuildFittedCoordinateSystem( + int offset, + string keyword, + char leftDelimiter, + string name, + object pmt, + object projcs, + object authority, + char rightDelimiter); + } +} diff --git a/src/ProjNet.IO.Wkt/Core/IWktOutputFormatter.cs b/src/ProjNet.IO.Wkt/Core/IWktOutputFormatter.cs new file mode 100644 index 0000000..2c95e0e --- /dev/null +++ b/src/ProjNet.IO.Wkt/Core/IWktOutputFormatter.cs @@ -0,0 +1,139 @@ +using System; +using System.Text; + +namespace ProjNet.IO.Wkt.Core +{ + + /// <summary> + /// IWktOutputFormatter interface for customizing ToString(...) support. + /// </summary> + public interface IWktOutputFormatter + { + /// <summary> + /// Newline char or empty string. Default empty. + /// </summary> + string Newline { get; } + + /// <summary> + /// LeftDelimiter if empty use original. Default empty. + /// </summary> + char? LeftDelimiter { get; } + /// <summary> + /// RightDelimiter if empty use original. Default empty. + /// </summary> + char? RightDelimiter { get; } + + /// <summary> + /// Separator to use. Default comma , + /// </summary> + string Separator { get; } + + /// <summary> + /// ExtraWhitespace. Default empty. + /// </summary> + string ExtraWhitespace { get; } + + + /// <summary> + /// Changeable Append method for Keywords. Optional extra indent is written. + /// </summary> + /// <param name="text"></param> + /// <param name="result"></param> + /// <param name="indent"></param> + /// <returns></returns> + IWktOutputFormatter AppendKeyword(string text, StringBuilder result, bool indent = true); + + + /// <summary> + /// Changeable Append method for Separator. Optional extra newline afterward. + /// </summary> + /// <param name="result"></param> + /// <param name="keepInside"></param> + /// <returns></returns> + IWktOutputFormatter AppendSeparator(StringBuilder result, bool keepInside = false); + + /// <summary> + /// Changeable Append method for string. + /// </summary> + /// <param name="text"></param> + /// <param name="result"></param> + /// <returns></returns> + IWktOutputFormatter Append(string text, StringBuilder result); + + /// <summary> + /// Changeable Append method for long. + /// </summary> + /// <param name="l"></param> + /// <param name="result"></param> + /// <returns></returns> + IWktOutputFormatter Append(long l, StringBuilder result); + + /// <summary> + /// Changeable Append method for double. + /// </summary> + /// <param name="d"></param> + /// <param name="result"></param> + /// <returns></returns> + IWktOutputFormatter Append(double d, StringBuilder result); + + + /// <summary> + /// Changeable AppendNewline method applying Newline. + /// </summary> + /// <param name="result"></param> + /// <returns></returns> + IWktOutputFormatter AppendNewline(StringBuilder result); + + /// <summary> + /// Changeable AppendQuotedText method applying text surrounded by double quotes. + /// </summary> + /// <param name="text"></param> + /// <param name="result"></param> + /// <returns></returns> + IWktOutputFormatter AppendQuotedText(string text, StringBuilder result); + + /// <summary> + /// Changeable AppendLeftDelimiter applying LeftDelimiter or original. + /// </summary> + /// <param name="original"></param> + /// <param name="result"></param> + /// <returns></returns> + IWktOutputFormatter AppendLeftDelimiter(char original, StringBuilder result); + + /// <summary> + /// Changeable AppendRightDelimiter applying RightDelimiter or original. + /// </summary> + /// <param name="original"></param> + /// <param name="result"></param> + /// <returns></returns> + IWktOutputFormatter AppendRightDelimiter(char original, StringBuilder result); + + + /// <summary> + /// Changeable AppendExtraWhitespace optionally applying extr whitespace. + /// </summary> + /// <param name="result"></param> + /// <returns></returns> + IWktOutputFormatter AppendExtraWhitespace(StringBuilder result); + + /// <summary> + /// IncreaseIndentCounter + /// </summary> + /// <returns></returns> + IWktOutputFormatter IncreaseIndentCounter(); + + /// <summary> + /// DecreaseIndentCounter + /// </summary> + /// <returns></returns> + IWktOutputFormatter DecreaseIndentCounter(); + + + /// <summary> + /// AppendIndent + /// </summary> + /// <param name="result"></param> + /// <returns></returns> + IWktOutputFormatter AppendIndent(StringBuilder result); + } +} diff --git a/src/ProjNet.IO.Wkt/Core/IWktTraverseHandler.cs b/src/ProjNet.IO.Wkt/Core/IWktTraverseHandler.cs new file mode 100644 index 0000000..e80d5c2 --- /dev/null +++ b/src/ProjNet.IO.Wkt/Core/IWktTraverseHandler.cs @@ -0,0 +1,108 @@ +using System.Collections.Generic; +using ProjNet.IO.Wkt.Tree; + +namespace ProjNet.IO.Wkt.Core +{ + /// <summary> + /// IWktTraverseHandler interface for traveling a WktObject tree. + /// </summary> + public interface IWktTraverseHandler + { + + /// <summary> + /// Handle method for WktAuthority. + /// </summary> + /// <param name="authority"></param> + void Handle(WktAuthority authority); + + /// <summary> + /// Handle method for WktAxis. + /// </summary> + /// <param name="axis"></param> + void Handle(WktAxis axis); + + /// <summary> + /// Handle method for WktDatum. + /// </summary> + /// <param name="datum"></param> + void Handle(WktDatum datum); + + /// <summary> + /// Handle method for WktEllipsoid. + /// </summary> + /// <param name="ellipsoid"></param> + void Handle(WktEllipsoid ellipsoid); + + /// <summary> + /// Handle method for WktSpheroid. + /// </summary> + /// <param name="spheroid"></param> + void Handle(WktSpheroid spheroid); + + /// <summary> + /// Handle method for WktExtension. + /// </summary> + /// <param name="extension"></param> + void Handle(WktExtension extension); + + /// <summary> + /// Handle method for WktUnit. + /// </summary> + /// <param name="unit"></param> + void Handle(WktUnit unit); + + /// <summary> + /// Handle method for WktParameter. + /// </summary> + /// <param name="parameter"></param> + void Handle(WktParameter parameter); + + /// <summary> + /// Handle method for WktParameterMathTransform. (FittedCoordinateSystem related). + /// </summary> + /// <param name="pmt"></param> + void Handle(WktParameterMathTransform pmt); + + /// <summary> + /// Handle method for WktPrimeMeridian. + /// </summary> + /// <param name="meridian"></param> + void Handle(WktPrimeMeridian meridian); + + /// <summary> + /// Handle method for WktProjection. + /// </summary> + /// <param name="projection"></param> + void Handle(WktProjection projection); + + /// <summary> + /// Handle method for WktToWgs84. + /// </summary> + /// <param name="toWgs84"></param> + void Handle(WktToWgs84 toWgs84); + + /// <summary> + /// Handle method for WktGeocentricCoordinateSystem. + /// </summary> + /// <param name="cs"></param> + void Handle(WktGeocentricCoordinateSystem cs); + + /// <summary> + /// Handle method for WktGeographicCoordinateSystem. + /// </summary> + /// <param name="cs"></param> + void Handle(WktGeographicCoordinateSystem cs); + + /// <summary> + /// Handle method for WktProjectedCoordinateSystem. + /// </summary> + /// <param name="cs"></param> + void Handle(WktProjectedCoordinateSystem cs); + + /// <summary> + /// Handle method for WktFittedCoordinateSystem. + /// </summary> + /// <param name="cs"></param> + void Handle(WktFittedCoordinateSystem cs); + } +} diff --git a/src/ProjNet.IO.Wkt/Core/WktCRS1Parser.cs b/src/ProjNet.IO.Wkt/Core/WktCRS1Parser.cs new file mode 100644 index 0000000..3def1f3 --- /dev/null +++ b/src/ProjNet.IO.Wkt/Core/WktCRS1Parser.cs @@ -0,0 +1,606 @@ +using ProjNet.IO.Wkt.Tree; +using ProjNet.IO.Wkt.Utils; +using System; +using System.Linq; + +using static ProjNet.IO.Wkt.Utils.Utils; +using Pidgin; +using static Pidgin.Parser; + + + +namespace ProjNet.IO.Wkt.Core +{ + /// <summary> + /// WKT1Parser - Base parser for WKT 1 using Parser Combinator Library Pidgin. + /// </summary> + /// <seealso href="https://github.com/benjamin-hodgson/Pidgin"/> + public class WktCRS1Parser + { + + // 6.3.1 Basic characters + + // <simple Latin upper case letter> + internal static readonly Parser<char, char> SimpleLatinUpperCaseLetter = OneOf( + Char('A'), Char('B'), Char('C'), + Char('D'), Char('E'), Char('F'), Char('G'), + Char('H'), Char('I'), Char('J'), Char('K'), + Char('L'), Char('M'), Char('N'), Char('O'), + Char('P'), Char('Q'), Char('R'), Char('S'), + Char('T'), Char('U'), Char('V'), Char('W'), + Char('X'), Char('Y'), Char('Z') + ); + + // <simple Latin lower case letter> + internal static readonly Parser<char, char> SimpleLatinLowerCaseLetter = OneOf( + Char('a'), Char('b'), Char('c'), + Char('d'), Char('e'), Char('f'), Char('g'), + Char('h'), Char('i'), Char('j'), Char('k'), + Char('l'), Char('m'), Char('n'), Char('o'), + Char('p'), Char('q'), Char('r'), Char('s'), + Char('t'), Char('u'), Char('v'), Char('w'), + Char('x'), Char('y'), Char('z') + ); + + // <digit> + internal static readonly Parser<char, char> Didgit = OneOf( + Char('0'), Char('1'), Char('2'), + Char('3'), Char('4'), Char('5'), + Char('6'), Char('7'), Char('8'), + Char('9') + ); + + // <space> + internal static readonly Parser<char, char> Space = Char(' '); + + // <double quote> + internal static readonly Parser<char, char> DoubleQuote = Char('"'); + + // <number sign> + internal static readonly Parser<char, char> NumberSign = Char('#'); + + // <percent> + internal static readonly Parser<char, char> Percent = Char('%'); + + // <ampersand> + internal static readonly Parser<char, char> Ampersand = Char('&'); + + // <quote> + internal static readonly Parser<char, char> Quote = Char('\''); + + // <left paren> + internal static readonly Parser<char, char> LeftParen = Char('('); + // <right paren> + internal static readonly Parser<char, char> RightParen = Char(')'); + + // <asterisk> + internal static readonly Parser<char, char> Asterisk = Char('*'); + + // <plus sign> + internal static readonly Parser<char, char> PlusSign = Char('+'); + + // <comma> + internal static readonly Parser<char, char> Comma = Char(','); + + // <minus sign> ::- <hyphen> + internal static readonly Parser<char, char> MinusSign = Char('-'); + internal static readonly Parser<char, char> Hyphen = Char('-'); + + // <period> + internal static readonly Parser<char, char> Period = Char('.'); + + // <solidus> + internal static readonly Parser<char, char> Solidus = Char('/'); + // <reverse solidus> + internal static readonly Parser<char, char> ReverseSolidus = Char('\\'); + + // <colon> + internal static readonly Parser<char, char> Colon = Char(':'); + // <semicolon> + internal static readonly Parser<char, char> SemiColon = Char(';'); + + // <less than operator> + internal static readonly Parser<char, char> LessThanOperator = Char('<'); + // <equals operator> + internal static readonly Parser<char, char> EqualsOperator = Char('='); + // <greater than operator> + internal static readonly Parser<char, char> GreaterThanOperator = Char('>'); + + // <question mark> + internal static readonly Parser<char, char> QuestionMark = Char('?'); + + // <left bracket> + internal static readonly Parser<char, char> LeftBracket = Char('['); + // <right bracket> + internal static readonly Parser<char, char> RightBracket = Char(']'); + + // <circumflex> + internal static readonly Parser<char, char> Circumflex = Char('^'); + + // <underscore> + internal static readonly Parser<char, char> Underscore = Char('_'); + + // <left brace> + internal static readonly Parser<char, char> LeftBrace = Char('{'); + // <right brace> + internal static readonly Parser<char, char> RightBrace = Char('}'); + + // <vertical bar> + internal static readonly Parser<char, char> VerticalBar = Char('|'); + + // <degree symbol> + internal static readonly Parser<char, char> DegreeSymbol = Char('\u00B0'); + + + // 6.3.2 Numbers + + // <sign> + internal static readonly Parser<char, char> Sign = OneOf(PlusSign, MinusSign); + // <unsigned integer> + internal static readonly Parser<char, string> UnsignedIntegerString = + WktCRS1Parser.Didgit.AtLeastOnceString(); + internal static readonly Parser<char, uint> UnsignedInteger = + UnsignedIntegerString + .Select(uint.Parse); + + // <exact numeric literal> + internal static readonly Parser<char, double> ExactNumericLiteralDotted = + Period + .Then(UnsignedIntegerString, (c, ui) => CalcAsFractionOf(0, ui)); + + internal static readonly Parser<char, double> ExactNumericLiteral = + UnsignedInteger.Optional() + .Then(ExactNumericLiteralDotted.Optional(), (ui, d) => ui.GetValueOrDefault() + d.GetValueOrDefault()); + + + // <signed integer> + internal static readonly Parser<char, int> SignedInteger = + Sign.Optional() + .Then(UnsignedInteger, (c, ui) => (int) ((c.HasValue && c.Value == '-' ? -1 : 1) * ui)); + + // <exponent> + internal static readonly Parser<char, int> Exponent = + SignedInteger; + + // <mantissa> + internal static readonly Parser<char, double> Mantissa = + ExactNumericLiteral; + + // <approximate numeric literal> + internal static readonly Parser<char, double> ApproximateNumericLiteralExp = + OneOf(Char('e'),Char('E')) + .Then(Exponent) + .Select(e => Math.Pow(10, e)); + internal static readonly Parser<char, double> ApproximateNumericLiteral = + Mantissa.Then(ApproximateNumericLiteralExp.Optional(), (m, e) => m * (e.HasValue ? e.Value : 1)); + + // <unsigned numeric literal> + internal static readonly Parser<char, double> UnsignedNumericLiteral = OneOf( + ApproximateNumericLiteral, + ExactNumericLiteral + ); + + // <signed numeric literal> + internal static readonly Parser<char, double> SignedNumericLiteral = + Sign.Optional() + .Then(UnsignedNumericLiteral, (s, d) => (double) ((s.HasValue && s.Value == '-' ? -1 : 1) * d)); + + + // <number> + internal static readonly Parser<char, double> Number = OneOf( + SignedNumericLiteral, + UnsignedNumericLiteral) + .Labelled("number"); + + + // 6.3.3 Date and time + + // <day> ::= <unsigned integer> !! two digits + internal static readonly Parser<char, uint> Day = WktCRS1Parser.Didgit + .Repeat(2) + .Select(d => uint.Parse(new string(d.ToArray()))); + // <month> ::= <unsigned integer> !! two digits + internal static readonly Parser<char, uint> Month = WktCRS1Parser.Didgit + .Repeat(2) + .Select(m => uint.Parse(new string(m.ToArray()))); + // <year> ::= <unsigned integer> !! four digits + internal static readonly Parser<char, uint> Year = WktCRS1Parser.Didgit + .Repeat(4) + .Select(y => uint.Parse(new string(y.ToArray()))); + + // <hour> ::= <unsigned integer> + // !! two digits including leading zero if less than 10 + internal static readonly Parser<char, uint> Hour = WktCRS1Parser.Didgit + .Repeat(2) + .Select(h => uint.Parse(new string(h.ToArray()))); + + // <minute> ::= <unsigned integer> + // !! two digits including leading zero if less than 10 + internal static readonly Parser<char, uint> Minute = WktCRS1Parser.Didgit + .Repeat(2) + .Select(h => uint.Parse(new string(h.ToArray()))); + + // <seconds integer> ::= <unsigned integer> + // !! two digits including leading zero if less than 10 + internal static readonly Parser<char, uint> SecondsInteger = WktCRS1Parser.Didgit + .Repeat(2) + .Select(h => uint.Parse(new string(h.ToArray()))); + // <seconds fraction> ::= <unsigned integer> + internal static readonly Parser<char, uint> SecondsFraction = UnsignedInteger; + + // <second> ::= <seconds integer> [ <period> [ <seconds fraction> ] ] + // !! In this International Standard the separator between the integer and fractional parts of a second value shall be a period. The ISO 8601 preference for comma is not permitted. + internal static readonly Parser<char, uint> SecondsDotted = Period.Then(SecondsFraction); + + internal static readonly Parser<char, DateTimeBuilder> Second = SecondsInteger.Then(SecondsDotted.Optional(), + (u, mf) => new DateTimeBuilder() + .SetSeconds((int) u) + .SetMilliseconds((int) mf.GetValueOrDefault())); + + // <utc designator> ::= Z + internal static readonly Parser<char, char> UtcDesignator = Char('Z'); + + // <local time zone designator> ::= { <plus sign> | <minus sign> } <hour> [ <colon> <minute> ] + internal static readonly Parser<char, uint> ColonMinute = Colon.Then(Minute); + internal static readonly Parser<char, TimeSpan> LocalTimeZoneDesignator = Sign + .Then(Hour, (ms, u) => (ms == '-' ? -1 : 1) * u) + .Then(ColonMinute.Optional(), (h, mm) => new TimeSpan(0, (int) h, (int) mm.GetValueOrDefault(), 0)); + + // <time zone designator> ::= <utc designator> | <local time zone designator> + internal static readonly Parser<char, TimeSpan> TimeZoneDesignator = OneOf( + UtcDesignator.Select(z => TimeSpan.Zero), + LocalTimeZoneDesignator + ); + + // <time designator> ::= T + internal static readonly Parser<char, char> TimeDesignator = Char('T'); + + // <24 hour clock> ::= <time designator> <hour> [ <colon> <minute> [ <colon> <second> ] ] <time zone designator> + internal static readonly Parser<char, DateTimeBuilder> ColonSecond = Colon.Then(Second); + + internal static readonly Parser<char, DateTimeBuilder> _24HourClock = TimeDesignator + .Then(Hour, (c, h) => new DateTimeBuilder().SetHour((int) h)) + .Then(ColonMinute.Optional(), (dtb, mm) => dtb.SetMinutes((int) mm.GetValueOrDefault())) + .Then(ColonSecond.Optional(), + (dtb1, dtb2) => + dtb2.HasValue + ? dtb1.SetSeconds((int) dtb2.GetValueOrDefault().Seconds) + .SetMilliseconds((int) dtb2.GetValueOrDefault().Milliseconds) + : dtb1) + .Then(TimeZoneDesignator, (dtb, ts) => dtb.SetLocalOffset(ts)); + + // <ordinal day> ::= <unsigned integer> !! three digits + internal static readonly Parser<char, uint> OrdinalDay = WktCRS1Parser.Didgit + .Repeat(3) + .Select(od => uint.Parse(new string(od.ToArray()))); + + // <Gregorian ordinal date> ::= <year> [ <hyphen> <ordinal day> ] + internal static readonly Parser<char, uint> GregorianOrdinalDateOptionalPart = Hyphen + .Then(OrdinalDay); + internal static readonly Parser<char, DateTimeBuilder> GregorianOrdinalDate = Year + .Then(GregorianOrdinalDateOptionalPart.Optional(), + (y, md) => new DateTimeBuilder().SetYear((int) y).SetOrdinalDay(md)); + + // <Gregorian ordinal datetime> ::= <Gregorian ordinal date> [ <24 hour clock> ] + internal static readonly Parser<char, DateTimeBuilder> GregorianOrdinalDateTime = GregorianOrdinalDate + .Then(_24HourClock.Optional(), + (dtb, mdtb) => + mdtb.HasValue ? + dtb.Merge(mdtb.GetValueOrDefault()) + : dtb); + + + // <Gregorian calendar date> ::= <year> [ <hyphen> <month> [ <hyphen> <day> ] ] + internal static readonly Parser<char, DateTimeBuilder> GregorianCalendarDateHyphenDay = Hyphen + .Then(Day, (hh, d) => new DateTimeBuilder().SetDay((int)d)); + + internal static readonly Parser<char, DateTimeBuilder> GregorianCalendarDateHyphenMonth = Hyphen + .Then(Month, (hh, m) => new DateTimeBuilder().SetMonth((int) m)) + .Then(GregorianCalendarDateHyphenDay.Optional(), + (dtbm, dtbd) => dtbm.SetDay((int) dtbd.GetValueOrDefault().Day)); + + internal static readonly Parser<char, DateTimeBuilder> GregorianCalendarDate = Year + .Then(GregorianCalendarDateHyphenMonth.Optional(), (y, dtb) => + dtb.HasValue ? dtb.GetValueOrDefault().SetYear((int) y) : new DateTimeBuilder().SetYear((int) y)); + + // <Gregorian calendar datetime> ::= <Gregorian calendar date> [ <24 hours clock>] + internal static readonly Parser<char, DateTimeBuilder> GregorianCalendarDateTime = + GregorianCalendarDate.Then(_24HourClock.Optional(), + (dtbdate, mdtbc) => mdtbc.HasValue ? dtbdate.Merge(mdtbc.Value) : dtbdate); + + // <datetime> ::= <Gregorian calendar datetime> | <Gregorian ordinal datetime> + internal static readonly Parser<char, DateTimeOffset> DateTimeParser = GregorianCalendarDateTime + .Or(GregorianOrdinalDateTime) + .Select(dtb => dtb.ToDateTimeOffset()); + + + // *** 6.3.4 CRS WKT characters *** + + // <doublequote symbol> ::= "" !! two double quote chars + internal static readonly Parser<char, string> DoubleQuoteSymbol = Try( + DoubleQuote.Repeat(2).Select(c => new string(c.ToArray()))); + + // <nondoublequote character> ::= !! A <nondoublequote character> is any character of the source language character set other than a <double quote>. + internal static readonly Parser<char, string> NonDoubleQuoteCharacter = + DoubleQuoteSymbol.Where(s => !DoubleQuote.Parse(s).Success); + + // <left delimiter> ::= <left bracket> | <left paren> + // !! In this International Standard the preferred left delimiter is <left bracket>. <left paren> is permitted for backward compatibility. Implementations shall be able to read both forms. + internal static readonly Parser<char, char> LeftDelimiter = LeftBracket.Or(LeftParen); + + // <right delimiter> ::= <right bracket> | <right paren> + internal static readonly Parser<char, char> RightDelimiter = RightBracket.Or(RightParen); + + // <wkt separator> ::= <comma> + internal static readonly Parser<char, char> WktSeparator = Comma; + + // <wkt Latin text character> ::= <simple Latin upper case letter> | <simple Latin lower case letter> | <digit> | <underscore> + // | <left bracket> | <right bracket> | <left paren> | <right paren> + // | <left brace> | <right brace> + // | <less than operator> | <equals operator> | <greater than operator> | <period> | <comma> | <colon> | <semicolon> + // | <plus sign> | <minus sign> | <space> | <number sign> + // | <percent> | <ampersand> | <quote> | <asterisk> | <circumflex> + // | <solidus> | <reverse solidus> | <question mark> | <vertical bar> + // | <degree symbol> | <doublequote symbol> + internal static readonly Parser<char, char> WktLatinTextCharacterChars = OneOf( + SimpleLatinUpperCaseLetter, SimpleLatinLowerCaseLetter, WktCRS1Parser.Didgit, Underscore, + LeftBracket, RightBracket, + LessThanOperator, EqualsOperator, GreaterThanOperator, Period, Comma, Colon, SemiColon, + PlusSign, MinusSign, Space, NumberSign, + Percent, Ampersand, Quote, Asterisk, Circumflex, + Solidus, ReverseSolidus, QuestionMark, VerticalBar, + DegreeSymbol + ); + internal static readonly Parser<char, string> WktLatinTextCharacter = + DoubleQuoteSymbol + .Or(WktLatinTextCharacterChars.Select(c => c.ToString())) + .Labelled("Wkt Latin Text Character"); + internal static readonly Parser<char, string> + WktLatinTextCharacters = WktLatinTextCharacter.AtLeastOnceString(); + + // <quoted Latin text> ::= <double quote> <wkt Latin text character>... <double quote> + internal static readonly Parser<char, string> QuotedLatinText = + WktLatinTextCharacters.Between(DoubleQuote, DoubleQuote) + .Labelled("Quoted Latin Text"); + + // <wkt Unicode text character> ::= <nondouble quote> | <doublequote symbol> + internal static readonly Parser<char, string> WktUnicodeTextCharacter = OneOf( + LetterOrDigit.Select(c => c.ToString()), + Space.Select(c => c.ToString()), + DoubleQuoteSymbol) + .Labelled("Wkt Unicode Text Character"); + internal static readonly Parser<char, string> + WktUnicodeTextCharacters = WktUnicodeTextCharacter.AtLeastOnceString(); + + internal static readonly Parser<char, string> QuotedUnicodeText = + WktUnicodeTextCharacters.Between(DoubleQuote, DoubleQuote) + .Labelled("Quoted Unicode Text"); + + + // 7.3.2 ScopeParser + + // <scope text description> ::= <quoted Latin text> + internal static readonly Parser<char, string> ScopeTextDescriptionParser = QuotedLatinText; + + // <scope keyword> ::= SCOPE + internal static readonly Parser<char, string> ScopeKeywordParser = String("SCOPE"); + + + // <scope> ::= <scope keyword> <left delimiter> <scope text description> <right delimiter> + internal static readonly Parser<char, WktScope> ScopeParser = ScopeKeywordParser + .Then(LeftDelimiter) + .Then(ScopeTextDescriptionParser, (c, s) => new WktScope{Description = s}) + .Before(RightDelimiter) + .Select(std => std); + + + // 7.3.3.2 Area description + + // <area text description> ::= <quoted Latin text> + internal static readonly Parser<char, string> AreaTextDescriptionParser = QuotedLatinText; + + // <area description keyword> ::= AREA + internal static readonly Parser<char, string> AreaDescriptionKeywordParser = String("AREA"); + + // <area description> ::= <area description keyword> <left delimiter> <area text description> <right delimiter> + internal static readonly Parser<char, AreaDescription> AreaDescriptionParser = AreaDescriptionKeywordParser + .Then(LeftDelimiter) + .Then(AreaTextDescriptionParser, (c, s) => new AreaDescription {Description = s}) + .Before(RightDelimiter); + + // 7.3.3.3 Geographic bounding box + + // <geographic bounding box keyword> ::= BBOX + internal static readonly Parser<char, string> GeographicBoundingBoxKeywordParser = String("BBOX"); + + // <lower left latitude> ::= <number> + internal static readonly Parser<char, double> LowerLeftLatitudeParser = Number; + // <lower left longitude> ::= <number> + internal static readonly Parser<char, double> LowerLeftLongitudeParser = Number; + + // <upper right latitude> ::= <number> + internal static readonly Parser<char, double> UpperRightLatitudeParser = Number; + // <upper right longitude> ::= <number> + internal static readonly Parser<char, double> UpperRightLongitudeParser = Number; + + // <geographic bounding box> ::= <geographic bounding box keyword> <left delimiter> <lower left latitude> <wkt separator> <lower left longitude> <wkt separator> <upper right latitude> <wkt separator> <upper right longitude> <right delimiter> + internal static readonly Parser<char, BBox> GeographicBoundingBoxParser = GeographicBoundingBoxKeywordParser + .Then(LeftDelimiter) + .Then(LowerLeftLatitudeParser, (c, d) => new BBox{LowerLeftLatitude = d} ).Before(WktSeparator) + .Then(LowerLeftLongitudeParser, (bbox, dLong) => + { + bbox.LowerLeftLongitude = dLong; + return bbox; + }).Before(WktSeparator) + .Then(UpperRightLatitudeParser, (bbox, dLat) => { bbox.UpperRightLatitude = dLat; + return bbox; + }).Before(WktSeparator) + .Then(UpperRightLongitudeParser, (bbox, dLong) => { bbox.UpperRightLongitude = dLong; + return bbox; + }) + .Before(RightDelimiter); + + + // 7.3.3.4 Vertical extent + + // <vertical extent minimum height> ::= <number> + internal static readonly Parser<char, double> VerticalExtentMinimumHeightParser = Number; + // <vertical extent maximum height> ::= <number> + internal static readonly Parser<char, double> VerticalExtentMaximumHeightParser = Number; + // <vertical extent keyword> ::= VERTICALEXTENT + internal static readonly Parser<char, string> VerticalExtentKeywordParser = String("VERTICALEXTENT"); + + // <vertical extent> ::= <vertical extent keyword> <left delimiter> <vertical extent minimum height> <wkt separator> <vertical extent maximum height> + // [ <wkt separator> <length unit> ] <right delimiter> + internal static readonly Parser<char, WktVerticalExtent> VerticalExtentParser = VerticalExtentKeywordParser + .Then(LeftDelimiter) + .Then(VerticalExtentMinimumHeightParser).Before(WktSeparator) + .Then(VerticalExtentMaximumHeightParser, + (min, max) => new WktVerticalExtent {MinimumHeight = min, MaximumHeight = max}) + // TODO: Add Optional LengthUnit support! + .Before(RightDelimiter); + + + // 7.3.3.5 Temporal extent + + // <temporal extent keyword> ::= TIMEEXTENT + internal static readonly Parser<char, string> TemporalExtentKeywordParser = String("TIMEEXTENT"); + + // <temporal extent start> ::= <datetime> | <quoted Latin text> + internal static readonly Parser<char, (DateTimeOffset?, string)> TemporalExtentStartParser = + DateTimeParser.Select(x => new ValueTuple<DateTimeOffset?, string>(x, null)) + .Or(QuotedLatinText.Select(x => new ValueTuple<DateTimeOffset?, string>(null, x))); + // <temporal extent end> ::= <datetime> | <quoted Latin text> + internal static readonly Parser<char, (DateTimeOffset?, string)> TemporalExtentEndParser = + DateTimeParser.Select(x => new ValueTuple<DateTimeOffset?, string>(x, null)) + .Or(QuotedLatinText.Select(x => new ValueTuple<DateTimeOffset?, string>(null, x))); + + // <temporal extent> ::= <temporal extent keyword> <left delimiter> + // <temporal extent start> <wkt separator> <temporal extent end> <right delimiter> + internal static readonly Parser<char, WktTemporalExtent> TemporalExtentParser = + TemporalExtentKeywordParser + .Then(LeftDelimiter) + .Then(TemporalExtentStartParser, + (c, tuple) => new WktTemporalExtent {StartDateTime = tuple.Item1, StartText = tuple.Item2}) + .Before(WktSeparator) + .Then(TemporalExtentEndParser, (extent, tuple) => + { + extent.EndDateTime = tuple.Item1; + extent.EndText = tuple.Item2; + return extent; + }) + .Before(RightDelimiter); + + // <extent> ::= <area description> | <geographic bounding box> | <vertical extent> | <temporal extent> + // !! Constraint: each extent type shall have a maximum occurrence of 1. + internal static readonly Parser<char, WktExtent> ExtentParser = OneOf( + AreaDescriptionParser.Cast<WktExtent>(), + GeographicBoundingBoxParser.Cast<WktExtent>(), + VerticalExtentParser.Cast<WktExtent>(), + TemporalExtentParser.Cast<WktExtent>() + ); + + + // 7.3.4 Identifier + + // <uri> ::= <quoted Latin text> + internal static readonly Parser<char, string> UriParser = QuotedLatinText; + // <uri keyword> ::= URI + internal static readonly Parser<char, string> UriKeywordParser = String("URI"); + // <id uri> ::= <uri keyword> <left delimiter> <uri> <right delimiter> + internal static readonly Parser<char, WktUri> IdUriParser = UriKeywordParser + .Then(LeftDelimiter) + .Then(UriParser) + .Before(RightDelimiter) + .Select(s => new WktUri(s)); + + // <citation> ::= <quoted Latin text> + internal static readonly Parser<char, string> CitationParser = QuotedLatinText; + // <citation keyword> ::= CITATION + internal static readonly Parser<char, string> CitationKeywordParser = String("CITATION"); + // <authority citation> ::= <citation keyword> <left delimiter> <citation> <right delimiter> + internal static readonly Parser<char, WktAuthorityCitation> AuthorityCitationParser = CitationKeywordParser + .Then(LeftDelimiter) + .Then(CitationParser) + .Before(RightDelimiter) + .Select(s => new WktAuthorityCitation(s)); + + // <version> ::= <number> | <quoted Latin text> + internal static readonly Parser<char, object> VersionParser = OneOf( + Try(QuotedLatinText.Cast<object>()), + Number.Cast<object>()); + + // <authority unique identifier> ::= <number> | <quoted Latin text> + internal static readonly Parser<char, object> AuthorityUniqueIdentifierParser = OneOf( + Try(QuotedLatinText.Cast<object>()), + Number.Cast<object>()); + // <authority name> ::= <quoted Latin text> + internal static readonly Parser<char, string> AuthorityNameParser = QuotedLatinText; + // <identifier keyword> ::= ID + internal static readonly Parser<char, string> IdentifierKeywordParser = String("ID"); + // <identifier> ::= <identifier keyword> <left delimiter> <authority name> + // <wkt separator> <authority unique identifier> + // [ <wkt separator> <version> ] [ <wkt separator> <authority citation> ] [ <wkt separator> <id uri> ] <right delimiter> + internal static readonly Parser<char, WktIdentifier> IdentifierParser = Try(IdentifierKeywordParser + .Then(LeftDelimiter) + .Then(AuthorityNameParser).Select(an => new WktIdentifier {AuthorityName = an}) + .Then(WktSeparator.Then(AuthorityUniqueIdentifierParser), (identifier, o) => + { + identifier.AuthorityUniqueIdentifier = o; + return identifier; + }) + .Then(Try(WktSeparator.Then(VersionParser)).Optional(), (identifier, version) => + { + identifier.Version = version.GetValueOrDefault(); + return identifier; + }) + .Then(Try(WktSeparator.Then(AuthorityCitationParser)).Optional(), (identifier, citation) => + { + identifier.AuthorityCitation = citation.GetValueOrDefault(); + return identifier; + }) + .Then(Try(WktSeparator.Then(IdUriParser)).Optional(), (identifier, uri) => + { + identifier.IdUri = uri.GetValueOrDefault(); + return identifier; + }) + .Before(RightDelimiter)); + + + // 7.3.5 Remark + // <remark keyword> ::= REMARK + internal static readonly Parser<char, string> RemarkKeywordParser = String("REMARK"); + + // <remark> ::= <remark keyword> <left delimiter> <quoted Unicode text> <right delimiter> + internal static readonly Parser<char, WktRemark> RemarkParser = RemarkKeywordParser + .Then(LeftDelimiter) + .Then(QuotedUnicodeText) + .Before(RightDelimiter) + .Select(s => new WktRemark(s)); + + + // <scope extent identifier remark> ::= [ <wkt separator> <scope> ] + // [ { <wkt separator> <extent> } ]... + // [ { <wkt separator> <identifier> } ]... + // [ <wkt separator> <remark>] + internal static readonly Parser<char, WktScopeExtentIdentifierRemarkElement> + ScopeExtentIdentifierRemarkElementParser = + WktSeparator.Then(ScopeParser).Optional() + .Then(WktSeparator.Then(ExtentParser).Many(), + (scope, extents) => + new WktScopeExtentIdentifierRemarkElement(scope.GetValueOrDefault(), extents.ToList())) + .Then(WktSeparator.Then(IdentifierParser).Many(), + (element, identifiers) => + { + element.Identifiers = identifiers.ToList(); + return element; + }) + .Then(WktSeparator.Then(RemarkParser).Optional(), (element, remark) => + { + element.Remark = remark.GetValueOrDefault(); + return element; + }); + + } +} + diff --git a/src/ProjNet.IO.Wkt/Core/WktParser.cs b/src/ProjNet.IO.Wkt/Core/WktParser.cs new file mode 100644 index 0000000..4868098 --- /dev/null +++ b/src/ProjNet.IO.Wkt/Core/WktParser.cs @@ -0,0 +1,809 @@ +using ProjNet.IO.Wkt.Tree; +using ProjNet.IO.Wkt.Utils; +using static ProjNet.IO.Wkt.Utils.Utils; +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using Pidgin; +using static Pidgin.Parser; +using static Pidgin.Parser<char>; + + +namespace ProjNet.IO.Wkt.Core +{ + /// <summary> + /// WktParser using the Pidgin Parser Combinator library. + /// </summary> + public static partial class WktParser + { + public struct Context + { + public IWktBuilder builder; + } + + + internal static Parser<char, T> BetweenWhitespace<T>(Parser<char, T> p) => p.Between(SkipWhitespaces); + + // 7.2 Component description + // 7.2.1 BNF Introduction + + // <minus sign> ::= - + internal static readonly Parser<char, char> MinusSignParser = Char('-'); + // <plus sign> ::= + + internal static readonly Parser<char, char> PlusSignParser = Char('+'); + + // <ampersand> ::= & + internal static readonly Parser<char, char> AmpersandParser = Char('&'); + + // <at sign> ::= @ + internal static readonly Parser<char, char> AtSignParser = Char('@'); + + // <equal sign> ::= = + internal static readonly Parser<char, char> EqualSignParser = Char('='); + + // <left paren> ::= ( + internal static readonly Parser<char, char> LeftParenParser = Char('('); + // <right paren> ::= ) + internal static readonly Parser<char, char> RightParenParser = Char(')'); + + // <left bracket> ::= [ + internal static readonly Parser<char, char> LeftBracketParser = Char('['); + // <right bracket> ::= ] + internal static readonly Parser<char, char> RightBracketParser = Char(']'); + + // <forward slash> ::= / + internal static readonly Parser<char, char> ForwardSlashParser = Char('/'); + // <backward slash> ::= \ + internal static readonly Parser<char, char> BackwardSlashParser = Char('\\'); + + // <period> ::= . + internal static readonly Parser<char, char> PeriodParser = Char('.'); + + // <double quote> ::= " + internal static readonly Parser<char, char> DoubleQuoteParser = Char('"'); + + // <quote> ::= ' + internal static readonly Parser<char, char> QuoteParser = Char('\''); + + // <comma> , + internal static readonly Parser<char, char> CommaParser = Char(','); + + // <underscore> ::= _ + internal static readonly Parser<char, char> UnderScoreParser = Char('_'); + + // <digit> ::= 0|1|2|3|4|5|6|7|8|9 + internal static readonly Parser<char, char> DigitParser = Digit; + + // <simple Latin lower case letter> ::= a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z + internal static readonly Parser<char, char> SimpleLatinLowerCaseLetterParser = OneOf( + Char('a'), Char('b'), Char('c'), + Char('d'), Char('e'), Char('f'), Char('g'), + Char('h'), Char('i'), Char('j'), Char('k'), + Char('l'), Char('m'), Char('n'), Char('o'), + Char('p'), Char('q'), Char('r'), Char('s'), + Char('t'), Char('u'), Char('v'), Char('w'), + Char('x'), Char('y'), Char('z') + ); + + // <simple Latin upper case letter> ::= A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z + internal static readonly Parser<char, char> SimpleLatinUpperCaseLetterParser = OneOf( + Char('A'), Char('B'), Char('C'), + Char('D'), Char('E'), Char('F'), Char('G'), + Char('H'), Char('I'), Char('J'), Char('K'), + Char('L'), Char('M'), Char('N'), Char('O'), + Char('P'), Char('Q'), Char('R'), Char('S'), + Char('T'), Char('U'), Char('V'), Char('W'), + Char('X'), Char('Y'), Char('Z') + ); + + // <space>= " " // unicode "U+0020" (space) + internal static readonly Parser<char, char> SpaceParser = Char(' '); + + // <left delimiter> ::= <left paren>|<left bracket> // must match balancing right delimiter + internal static readonly Parser<char, char> LeftDelimiterParser = BetweenWhitespace(LeftParenParser.Or(LeftBracketParser)); + // <right delimiter> ::= <right paren>|<right bracket> // must match balancing left delimiter + internal static readonly Parser<char, char> RightDelimiterParser = BetweenWhitespace(RightParenParser.Or(RightBracketParser)); + + // <special> ::= <right paren>|<left paren>|<minus sign> |<underscore>|<period>|<quote>|<space> + internal static readonly Parser<char, char> SpecialParser = OneOf( + RightParenParser, + LeftParenParser, + MinusSignParser, + PlusSignParser, + UnderScoreParser, + PeriodParser, + CommaParser, + EqualSignParser, + AtSignParser, + QuoteParser, + SpaceParser, + ForwardSlashParser, + BackwardSlashParser, + LeftBracketParser, + RightBracketParser, + AmpersandParser + ); + + // <sign> ::= <plus sign> | <minus sign> + internal static readonly Parser<char, char> SignParser = PlusSignParser.Or(MinusSignParser); + + // <decimal point> ::= <period> | <comma> // <== This is what definition states but comma doesn't work in example(s). + internal static readonly Parser<char, char> DecimalPointParser = PeriodParser;//.Or(CommaParser); + + // <empty set> ::= EMPTY + internal static readonly Parser<char, string> EmptySetParser = String("EMPTY"); + + // <wkt separator> ::= <comma> + internal static readonly Parser<char, char> WktSeparatorParser = BetweenWhitespace(CommaParser); + + + // <unsigned integer> ::= (<digit>)* + internal static readonly Parser<char, string> UnsignedIntegerStringParser = DigitParser.AtLeastOnceString(); + internal static readonly Parser<char, uint> UnsignedIntegerParser = UnsignedIntegerStringParser + .Select(uint.Parse); + + + // <signed integer> ::= {<sign>}<unsigned integer> + internal static readonly Parser<char, string> SignedIntegerStringParser = SignParser.Optional() + .Then(DigitParser.ManyString(), (sign, str) => sign.HasValue && sign.Value == '-' ? $"-{str}" : str); + internal static readonly Parser<char, int> SignedIntegerParser = SignedIntegerStringParser.Select(int.Parse); + + + // <exact numeric literal> ::= <unsigned integer>{<decimal point>{<unsigned integer>}} |<decimal point><unsigned integer> + internal static readonly Parser<char, double> ExactNumericLiteralDottedParser = + DecimalPointParser + .Then(UnsignedIntegerStringParser, (c, ui) => CalcAsFractionOf(0, ui)); + + internal static readonly Parser<char, double> ExactNumericLiteralParser = + UnsignedIntegerStringParser.Optional() + .Then(ExactNumericLiteralDottedParser.Optional(), (ui, d) => (ui.HasValue ? uint.Parse(ui.GetValueOrDefault()) : 0) + d.GetValueOrDefault()); + + // <mantissa> ::= <exact numeric literal> + internal static readonly Parser<char, double> MantissaParser = ExactNumericLiteralParser; + + // <exponent> ::= <signed integer> + internal static readonly Parser<char, string> ExponentStringParser = SignedIntegerStringParser; + internal static readonly Parser<char, int> ExponentParser = SignedIntegerParser; + + // <approximate numeric literal> ::= <mantissa>E<exponent> + internal static readonly Parser<char, double> ApproximateNumericLiteralExpParser = + OneOf(Char('e'),Char('E')) + .Then(ExponentParser) + .Select(e => Math.Pow(10, e)); + internal static readonly Parser<char, double> ApproximateNumericLiteralParser = + MantissaParser.Then(ApproximateNumericLiteralExpParser.Optional(), (m, e) => m * (e.HasValue ? e.Value : 1)); + + // <simple Latin letter> ::= <simple Latin upper case letter>|<simple Latin lower case letter> + internal static readonly Parser<char, char> SimpleLatinLetterParser = Letter; + //SimpleLatinUpperCaseLetterParser.Or(SimpleLatinLowerCaseLetterParser); + + // <unsigned numeric literal> ::= <exact numeric literal> | <approximate numeric literal> + internal static readonly Parser<char, double> UnsignedNumericLiteralParser = ExactNumericLiteralParser + .Or(ApproximateNumericLiteralParser); + + // <signed numeric literal> ::= {<sign>}<unsigned numeric literal> + internal static readonly Parser<char, double> SignedNumericLiteralParser = BetweenWhitespace( + SignParser.Optional() + .Then(UnsignedNumericLiteralParser, (sign, d) => + { + int signFactor = sign.GetValueOrDefault() == '-' ? -1 : 1; + return signFactor * d; + } )); + + // <x> ::= <signed numeric literal> + internal static readonly Parser<char, double> XParser = SignedNumericLiteralParser; + // <y> ::= <signed numeric literal> + internal static readonly Parser<char, double> YParser = SignedNumericLiteralParser; + // <z> ::= <signed numeric literal> + internal static readonly Parser<char, double> ZParser = SignedNumericLiteralParser; + // <m> ::= <signed numeric literal> + internal static readonly Parser<char, double> MParser = SignedNumericLiteralParser; + + // <letter> ::= <simple Latin letter>|<digit>|<special> + // NOTE: This one is very slow comparing to using AnyCharExcept! + internal static readonly Parser<char, char> LetterParser = OneOf(SimpleLatinLetterParser, DigitParser, SpecialParser); + // <letters> ::= (<letter>)* + internal static readonly Parser<char, string> LettersParser = AnyCharExcept('\"').ManyString(); + + // <name> ::= <letters> + internal static readonly Parser<char, string> NameParser = SimpleLatinLetterParser.Or(DigitParser).ManyString(); + // <quoted name> ::= <double quote> <name> <double quote> + internal static readonly Parser<char, string> QuotedNameParser = + LettersParser.Between(DoubleQuoteParser, DoubleQuoteParser); + + + + // 7.2.2 BNF Productions for Two-Dimension Geometry WKT + + // The following BNF defines two-dimensional geometries in (x, y) coordinate spaces. With the exception of the + // addition of polyhedral surfaces, these structures are unchanged from earlier editions of this standard. + // <point> ::= <x> <y> + // <geometry tagged text> ::= <point tagged text> + // | <linestring tagged text> + // | <polygon tagged text> + // | <triangle tagged text> + // | <polyhedralsurface tagged text> + // | <tin tagged text> + // | <multipoint tagged text> + // | <multilinestring tagged text> + // | <multipolygon tagged text> + // | <geometrycollection tagged text> + // <point tagged text> ::= point <point text> + // <linestring tagged text> ::= linestring <linestring text> + // <polygon tagged text> ::= polygon <polygon text> + // <polyhedralsurface tagged text> ::= polyhedralsurface + // <polyhedralsurface text> + // <triangle tagged text> ::= triangle <polygon text> + // <tin tagged text> tin <polyhedralsurface text> + // <multipoint tagged text> ::= multipoint <multipoint text> + // <multilinestring tagged text> ::= multilinestring <multilinestring text> + // <multipolygon tagged text> ::= multipolygon <multipolygon text> + // <geometrycollection tagged text> ::= geometrycollection + // <geometrycollection text> + // <point text> ::= <empty set> | <left paren> <point> <right paren> + // <linestring text> ::= <empty set> | <left paren> + // <point> + // {<comma> <point>}* + // <right paren> + // <polygon text> ::= <empty set> | <left paren> + // <linestring text> + // {<comma> <linestring text>}* + // <right paren> + // <polyhedralsurface text> ::= <empty set> | <left paren> + // <polygon text> + // {<comma> <polygon text>}* + // <right paren> + // <multipoint text> ::= <empty set> | <left paren> + // <point text> + // {<comma> <point text>}* + // <right paren> + // <multilinestring text> ::= <empty set> | <left paren> + // <linestring text> + // {<comma> <linestring text>}* + // <right paren> + // <multipolygon text> ::= <empty set> | <left paren> + // <polygon text> + // {<comma> <polygon text>}* + // <right paren> + // <geometrycollection text> ::= <empty set> | <left paren> + // <geometry tagged text> + // {<comma> <geometry tagged text>}* + // <right paren> + + + // 7.2.3 BNF Productions for Three-Dimension Geometry WKT + + // The following BNF defines geometries in 3 dimensional (x, y, z) coordinates. + // <point z> ::= <x> <y> <z> + // <geometry z tagged text> ::= <point z tagged text> + // |<linestring z tagged text> + // |<polygon z tagged text> + // |<polyhedronsurface z tagged text> + // |<triangle tagged text> + // |<tin tagged text> + // |<multipoint z tagged text> + // |<multilinestring z tagged text> + // |<multipolygon z tagged text> + // |<geometrycollection z tagged text> + // <point z tagged text> ::= point z <point z text> + // <linestring z tagged text> ::= linestring z <linestring z text> + // <polygon z tagged text> ::= polygon z <polygon z text> + // <polyhedralsurface z tagged text> ::= polyhedralsurface z + // <polyhedralsurface z text> + // <triangle z tagged text> ::= triangle z <polygon z text> + // <tin z tagged text> tin z <polyhedralsurface z text> + // <multipoint z tagged text> ::= multipoint z <multipoint z text> + // <multilinestring z tagged text> ::= multilinestring z <multilinestring z text> + // <multipolygon z tagged text> ::= multipolygon z <multipolygon z text> + // <geometrycollection z tagged + // text> ::= + // geometrycollection z + // <geometrycollection z text> + // <point z text> ::= <empty set> | <left paren> <point z> + // <right paren> + // <linestring z text> ::= <empty set> | <left paren> <point z> + // {<comma> <point z>}* + // <right paren> + // <polygon z text> ::= <empty set> | <left paren> + // <linestring z text> + // {<comma> <linestring z text>}* + // <right paren> + // <polyhedralsurface z text> ::= <empty set>|<left paren> + // <polygon z text> + // {<comma> <polygon z text>}* + // <right paren> + // <multipoint z text> ::= <empty set> | <left paren> + // <point z text> + // {<comma> <point z text>}* + // <right paren> + // <multilinestring z text> ::= <empty set> | <left paren> + // <linestring z text> + // {<comma> <linestring z text>}* + // <right paren> + // <multipolygon z text> ::= <empty set> | <left paren> + // <polygon z text> + // {<comma> <polygon z text>}* + // <right paren> + // <geometrycollection z text> ::= <empty set> | <left paren> + // <geometry tagged z text> + // {<comma> <geometry tagged z text>}* + // <right paren> + + + // 7.2.4 BNF Productions for Two-Dimension Measured Geometry WKT + + // The following BNF defines two-dimensional geometries in (x, y) coordinate spaces. In addition, each coordinate + // carries an "m" ordinate value that is part of some linear reference system. + // <point m> ::= <x> <y> <m> + // <geometry m tagged text> ::= <point m tagged text> + // |<linestring m tagged text> + // |<polygon m tagged text> + // |<polyhedralsurface m tagged text> + // |<triangle tagged m text> + // |<tin tagged m text> + // |<multipoint m tagged text> + // |<multilinestring m tagged text> + // |<multipolygon m tagged text> + // |<geometrycollection m tagged text> + // <point m tagged text> ::= point m <point m text> + // <linestring m tagged text> ::= linestring m <linestring m text> + // <polygon m tagged text> ::= polygon m <polygon m text> + // <polyhedralsurface m tagged text> ::= polyhedralsurface m + // <polyhedralsurface m text> + // <triangle m tagged text> ::= triangle m <polygon m text> + // <tin m tagged text> tin m <polyhedralsurface m text> + // <multipoint m tagged text> ::= multipoint m <multipoint m text> + // <multilinestring m tagged text> ::= multilinestring m <multilinestring m text> + // <multipolygon m tagged text> ::= multipolygon m <multipolygon m text> + // <geometrycollection m tagged + // text> ::= + // geometrycollection m + // <geometrycollection m text> + // <point m text> ::= <empty set> | <left paren> + // <point m> + // <right paren> + // <linestring m text> ::= <empty set> | <left paren> + // <point m> + // {{<comma> <point m>}+ + // <right paren> + // <polygon m text> ::= <empty set> | <left paren> + // <linestring m text> + // {<comma> <linestring m text>}* + // <right paren> + // <polyhedralsurface m text> ::= <empty set> | <left paren> + // <polygon m text> + // {<comma> <polygon m text>}* + // <right paren> + // <multipoint m text> ::= <empty set> | <left paren> <point m text> + // {<comma> <point m text>}* + // <right paren> + // <multilinestring m text> ::= <empty set> | <left paren> + // <linestring m text> + // {<comma> <linestring m text>}* + // <right paren> + // <multipolygon m text> ::= <empty set> | <left paren> + // <polygon m text> + // {<comma> <polygon m text>}* + // <right paren> + // <geometrycollection m text> ::= <empty set> | <left paren> + // <geometry tagged m text> + // {<comma> <geometry tagged m text>}* + // <right paren> + + + // 7.2.5 BNF Productions for Three-Dimension Measured Geometry WKT + + // The following BNF defines three-dimensional geometries in (x, y, z) coordinate spaces. In addition, each + // coordinate carries an "m" ordinate value that is part of some linear reference system. + // <point zm> ::= <x> <y> <z> <m> + // <geometry zm tagged text> ::= <point zm tagged text> + // |<linestring zm tagged text> + // |<polygon zm tagged text> + // |<polyhedralsurface zm tagged text> + // |<triangle zm tagged text> + // |<tin zm tagged text> + // |<multipoint zm tagged text> + // |<multilinestring zm tagged text> + // |<multipolygon zm tagged text> + // |<geometrycollection zm tagged text> + // <point zm tagged text> ::= point zm <point zm text> + // <linestring zm tagged text> ::= linestring zm <linestring zm text> + // <polygon zm tagged text> ::= polygon zm <polygon zm text> + // <polyhedralsurface zm tagged + // text> ::= + // polyhedralsurface zm + // <polyhedralsurface zm text> + // <triangle zm tagged text> ::= triangle zm <polygon zm text> + // <tin zm tagged text> tin zm <polyhedralsurface zm text> + // <multipoint zm tagged text> ::= multipoint zm <multipoint zm text> + // <multipoint zm tagged text> ::= multipoint zm + // <multipoint zm text> + // <multilinestring zm tagged text> ::= multilinestring zm + // <multilinestring zm text> + // <multipolygon zm tagged text> ::= multipolygon zm + // <MultiPolygon zm text> + // <geometrycollection zm tagged + // text> ::= + // geometrycollection zm + // <geometrycollection zm text> + // <point zm text> ::= <empty set> | <left paren> <point zm> + // <right paren> + // <linestring zm text> ::= <empty set> | <left paren> + // <point z> + // {<comma> <point z>}* + // <right paren> + // <polygon zm text> ::= <empty set> | <left paren> + // <linestring zm text> + // {<comma> <linestring zm text>}* + // <right paren> + // <polyhedralsurface zm text> ::= <empty set> | <left paren> { + // <polygon zm text + // {<comma> <polygon zm text>}*) + // <right paren> + // <multipoint zm text> ::= <empty set> | <left paren> + // <point zm text> + // {<comma> <point zm text>}* + // <right paren> + // <multilinestring zm text> ::= <empty set> | <left paren> + // <linestring zm text> + // {<comma> <linestring zm text>}* + // <right paren> + // <multipolygon zm text> ::= <empty set> | <left paren> + // <polygon zm text> + // {<comma> <polygon zm text>}* <right paren> + // <geometrycollection zm text> ::= <empty set> | <left paren> + // <geometry tagged zm text> + // {<comma> <geometry tagged zm text>}* <right paren> + + + + // 9 Well-known Text Representation of Spatial Reference System + + + // <value> ::= <signed numeric literal> + internal static readonly Parser<char, double> ValueParser = SignedNumericLiteralParser; + + // <semi-major axis> ::= <signed numeric literal> + internal static readonly Parser<char, double> SemiMajorAxisParser = SignedNumericLiteralParser; + + // <longitude> ::= <signed numeric literal> + internal static readonly Parser<char, double> LongitudeParser = SignedNumericLiteralParser; + + // <inverse flattening> ::= <signed numeric literal> + internal static readonly Parser<char, double> InverseFlatteningParser = SignedNumericLiteralParser; + + // <conversion factor> ::= <signed numeric literal> + internal static readonly Parser<char, double> ConversionFactorParser = SignedNumericLiteralParser; + + + // <unit name> ::= <quoted name> + internal static readonly Parser<char, string> UnitNameParser = QuotedNameParser; + + // <spheroid name> ::= <quoted name> + internal static readonly Parser<char, string> SpheroidNameParser = QuotedNameParser; + + // <projection name> ::= <quoted name> + internal static readonly Parser<char, string> ProjectionNameParser = QuotedNameParser; + + // <prime meridian name> ::= <quoted name> + internal static readonly Parser<char, string> PrimeMeridianNameParser = QuotedNameParser; + + // <parameter name> ::= <quoted name> + internal static readonly Parser<char, string> ParameterNameParser = QuotedNameParser; + + // <datum name> ::= <quoted name> + internal static readonly Parser<char, string> DatumNameParser = QuotedNameParser; + + // <csname> ::= <quoted name> + internal static readonly Parser<char, string> CsNameParser = QuotedNameParser; + + + + internal static Parser<char, object> AuthorityParser(Context ctx) => + // AUTHORITY = 'AUTHORITY' '[' string ',' string ']' + Map(ctx.builder.BuildAuthority, + CurrentOffset, + String("AUTHORITY"), + LeftDelimiterParser, + QuotedNameParser, + WktSeparatorParser.Then(Try(QuotedNameParser.Select(c => + { + if (int.TryParse(c, NumberStyles.Any, CultureInfo.InvariantCulture, out int code)) + return code; + return -1; + })).Or(Num)), + RightDelimiterParser) + .Labelled("WktAuthority"); + + internal static Parser<char, object> AxisParser(Context ctx) => + // AXIS = 'AXIS' '[' string ',' string ']' + Map(ctx.builder.BuildAxis, + CurrentOffset, + String("AXIS"), + LeftDelimiterParser, + QuotedNameParser, + WktSeparatorParser.Then(NameParser), + RightDelimiterParser) + .Labelled("WktAxis"); + + + internal static Parser<char, object> ExtensionParser(Context ctx) => + Map(ctx.builder.BuildExtension, + CurrentOffset, + String("EXTENSION"), + LeftDelimiterParser, + QuotedNameParser, + WktSeparatorParser.Then(LettersParser.Between(DoubleQuoteParser)), + RightDelimiterParser) + .Labelled("WktExtension"); + + internal static Parser<char, object> ToWgs84Parser(Context ctx) => + // TOWGS84 = 'TOWGS84' '[' float ',' float ',' float ',' float ',' float ',' float ',' float ']' + // Note: Map only takes 8 parsers max. So combining sub parsers first for shifts and rotations. + Map((offset, kw, ld, shifts, rotations, description, rd) => + ctx.builder.BuildToWgs84(offset, kw, ld, + shifts.dx, shifts.dy, shifts.dz, + rotations.ex, rotations.ey, rotations.ez, rotations.ppm, + description.GetValueOrDefault(), rd), + CurrentOffset, + String("TOWGS84"), + LeftDelimiterParser, + Map((dx, dy, dz) => (dx, dy, dz), + ValueParser, + WktSeparatorParser.Then(ValueParser), + WktSeparatorParser.Then(ValueParser)), + Map((ex, ey, ez, ppm) => (ex, ey, ez, ppm), + WktSeparatorParser.Then(ValueParser), + WktSeparatorParser.Then(ValueParser), + WktSeparatorParser.Then(ValueParser), + WktSeparatorParser.Then(ValueParser)), + QuotedNameParser.Optional(), + RightDelimiterParser) + .Labelled("WktToWgs84"); + + internal static Parser<char, object> ProjectionParser(Context ctx) => + // <projection> ::= PROJECTION <left delimiter> <projection name> <right delimiter> + Map((offset, kw, ld, name, authority, rd) => + ctx.builder.BuildProjection(offset, kw, ld, name, authority.GetValueOrDefault(),rd), + CurrentOffset, + String("PROJECTION"), + LeftDelimiterParser, + ProjectionNameParser, + WktSeparatorParser.Then(AuthorityParser(ctx)).Optional(), + RightDelimiterParser) + .Labelled("WktProjection"); + + internal static Parser<char, object> ProjectionParameterParser(Context ctx) => + // <parameter> ::= PARAMETER <left delimiter> <parameter name> <comma> <value> <right delimiter> + Map(ctx.builder.BuildProjectionParameter, + CurrentOffset, + String("PARAMETER"), + LeftDelimiterParser, + ParameterNameParser, + WktSeparatorParser.Then(ValueParser), + RightDelimiterParser) + .Labelled("WktParameter"); + + internal static Parser<char, object> EllipsoidParser(Context ctx) => + // Note: Ellipsoid and Spheroid have the same parser and object implementation. Separating them for clarity. + // <ellipsoid> ::= ELLIPSOID <left delimiter> <ellipsoid name> <comma> <semi-major axis> <comma> <inverse flattening> <right delimiter> + Map((offset, kw,ld, tuple, rd) => + ctx.builder.BuildEllipsoid(offset, kw, ld, tuple.name, tuple.semiMajorAxis, tuple.inverseFlatening, tuple.authority.GetValueOrDefault(), rd), + CurrentOffset, + String("ELLIPSOID"), + LeftDelimiterParser, + Map((name, semiMajorAxis, inverseFlatening, authority) => + (name, semiMajorAxis, inverseFlatening, authority), + SpheroidNameParser, + WktSeparatorParser.Then(SemiMajorAxisParser), + WktSeparatorParser.Then(InverseFlatteningParser), + WktSeparatorParser.Then(AuthorityParser(ctx)).Optional()), + RightDelimiterParser) + .Labelled("WktEllipsoid"); + + internal static Parser<char, object> SpheroidParser(Context ctx) => + // <spheroid> ::= SPHEROID <left delimiter> <spheroid name> <comma> <semi-major axis> <comma> <inverse flattening> <right delimiter> + Map((offset, kw,ld, tuple, rd) => + ctx.builder.BuildSpheroid(offset, kw, ld, tuple.name, tuple.semiMajorAxis, tuple.inverseFlatening, tuple.authority.GetValueOrDefault(), rd), + CurrentOffset, + String("SPHEROID"), + LeftDelimiterParser, + Map((name, semiMajorAxis, inverseFlatening, authority) => + (name, semiMajorAxis, inverseFlatening, authority), + SpheroidNameParser, + WktSeparatorParser.Then(SemiMajorAxisParser), + WktSeparatorParser.Then(InverseFlatteningParser), + WktSeparatorParser.Then(AuthorityParser(ctx)).Optional()), + RightDelimiterParser) + .Labelled("WktSpehroid"); + + + internal static Parser<char, object> PrimeMeridianParser(Context ctx) => + // <prime meridian> ::= PRIMEM <left delimiter> <prime meridian name> <comma> <longitude> [<comma> <authority>] <right delimiter> + Map((offset, kw, ld, name, longitude, authority, rd) => + ctx.builder.BuildPrimeMeridian(offset, kw, ld, name, longitude, authority.GetValueOrDefault(), rd), + CurrentOffset, + String("PRIMEM"), + LeftDelimiterParser, + PrimeMeridianNameParser, + WktSeparatorParser.Then(LongitudeParser), + WktSeparatorParser.Then(AuthorityParser(ctx)).Optional(), + RightDelimiterParser) + .Labelled("WktPrimeMeridian"); + + + internal static Parser<char, object> UnitParser(Context ctx) => + // <unit> ::= UNIT <left delimiter> <unit name> <comma> <conversion factor> [<comma> <authority>] <right delimiter> + Map((offset,kw, ld, name, factor, authority, rd) => + { + switch (name) + { + case "degree": + return ctx.builder.BuildAngularUnit(offset, kw, ld, name, factor, authority.GetValueOrDefault(), rd); + case "metre": + return ctx.builder.BuildLinearUnit(offset, kw, ld, name, factor, authority.GetValueOrDefault(), rd); + default: + return ctx.builder.BuildUnit(offset, kw, ld, name, factor, authority.GetValueOrDefault(), rd); + } + }, + CurrentOffset, + String("UNIT"), + LeftDelimiterParser, + UnitNameParser, + WktSeparatorParser.Then(ConversionFactorParser), + WktSeparatorParser.Then(AuthorityParser(ctx)).Optional(), + RightDelimiterParser) + .Labelled("WktUnit"); + + + internal static Parser<char, object> DatumParser(Context ctx) => + // <datum> ::= DATUM <left delimiter> <datum name> <comma> <spheroid> <right delimiter> + // Note: UnitTests showed that TOWGS84 is usable inside a DATUM element. + Map((offset, kw, ld, tuple, rd) => + ctx.builder.BuildDatum(offset, kw, ld, tuple.name, tuple.spheroid, tuple.towgs84.GetValueOrDefault(), tuple.authority.GetValueOrDefault(), rd), + CurrentOffset, + String("DATUM"), + LeftDelimiterParser, + Map((name, spheroid, towgs84, authority) => (name, spheroid, towgs84, authority), + DatumNameParser, + WktSeparatorParser.Then(SpheroidParser(ctx)), + Try(WktSeparatorParser.Then(ToWgs84Parser(ctx))).Optional(), + WktSeparatorParser.Then(AuthorityParser(ctx)).Optional()), + RightDelimiterParser) + .Labelled("WktDatum"); + + + internal static Parser<char, object> ParameterParser(Context ctx) => + // Fitted CoordinateSystem parser(s) + Map(ctx.builder.BuildParameter, + CurrentOffset, + String("PARAMETER"), + LeftDelimiterParser, + ParameterNameParser, + WktSeparatorParser.Then(ValueParser), + RightDelimiterParser) + .Labelled("WktParameter"); + + + internal static Parser<char, object> ParameterMathTransformParser(Context ctx) => + Map(ctx.builder.BuildParameterMathTransform, + CurrentOffset, + String("PARAM_MT"), + LeftDelimiterParser, + ParameterNameParser, + WktSeparatorParser.Then(ParameterParser(ctx)).Many(), + RightDelimiterParser) + .Labelled("WktParameterMathTransform"); + + + internal static Parser<char, object> FittedCsParser(Context ctx) => + Map((offset, kw, ld, tuple, rd) => + ctx.builder.BuildFittedCoordinateSystem(offset, kw, ld, + tuple.name, tuple.pmt, tuple.projcs, tuple.authority.GetValueOrDefault(), + rd), + CurrentOffset, + String("FITTED_CS"), + LeftDelimiterParser, + Map((name, pmt, projcs, authority) => (name, pmt, projcs, authority), + CsNameParser, + WktSeparatorParser.Then(ParameterMathTransformParser(ctx)), + WktSeparatorParser.Then(ProjectedCsParser(ctx)), + WktSeparatorParser.Then(AuthorityParser(ctx)).Optional()), + RightDelimiterParser) + .Labelled("WktFittedCoordinateSystem"); + + + internal static Parser<char, object> GeographicCsParser(Context ctx) => + // <geographic cs> ::= GEOGCS <left delimiter> <csname> + // <comma> <datum> <comma> <prime meridian> + // <comma> <angular unit> (<comma> <linear unit> ) + // <right delimiter> + Map((offset, kw, ld,tuple, rd) => + ctx.builder.BuildGeographicCoordinateSystem(offset, kw, ld, tuple.name, tuple.datum, tuple.meridian, tuple.unit.GetValueOrDefault(), tuple.axes, tuple.authority.GetValueOrDefault(),rd), + CurrentOffset, + String("GEOGCS"), + LeftDelimiterParser, + Map((name, datum, meridian, unit, axes,authority) => (name, datum, meridian, unit, axes, authority), + CsNameParser, + WktSeparatorParser.Then(DatumParser(ctx)), + WktSeparatorParser.Then(PrimeMeridianParser(ctx)), + WktSeparatorParser.Then(UnitParser(ctx)).Optional(), + Try(WktSeparatorParser.Then(AxisParser(ctx))).Many(), + WktSeparatorParser.Then(AuthorityParser(ctx)).Optional()), + RightDelimiterParser) + .Labelled("WktGeographicCoordinateSystem"); + + internal static Parser<char, object> GeocentricCsParser(Context ctx) => + // <geocentric cs> ::= GEOCCS <left delimiter> <name> <comma> <datum> <comma> <prime meridian> + // <comma> <linear unit> <right delimiter> + Map((offset, kw, ld, tuple, rd) => + ctx.builder.BuildGeocentricCoordinateSystem(offset, kw, ld, + tuple.name, tuple.datum, tuple.meridian, tuple.unit, tuple.axes, tuple.authority.GetValueOrDefault(), + rd), + CurrentOffset, + String("GEOCCS").Or(String("GEOCS")), + LeftDelimiterParser, + Map((name, datum, meridian, unit, axes, authority) => (name, datum, meridian, unit, axes, authority), + QuotedNameParser, + WktSeparatorParser.Then(DatumParser(ctx)), + WktSeparatorParser.Then(PrimeMeridianParser(ctx)), + WktSeparatorParser.Then(UnitParser(ctx)), + Try(WktSeparatorParser.Then(AxisParser(ctx))).Many(), + WktSeparatorParser.Then(AuthorityParser(ctx)).Optional()), + RightDelimiterParser) + .Labelled("WktGeocentricCoordinateSystem"); + + + internal static Parser<char, object> ProjectedCsParser(Context ctx) => + // <projected cs> ::= PROJCS <left delimiter> <csname> <comma> <geographic cs> <comma> <projection> + // (<comma> <parameter> )* <comma> <linear unit> <right delimiter> + Map((offset, kw, ld, tuple, rd) => + ctx.builder.BuildProjectedCoordinateSystem(offset, kw, ld, + tuple.name, tuple.geogcs, tuple.projection, tuple.parameters, tuple.unit.GetValueOrDefault(), + tuple.axes, tuple.extension, tuple.authority.GetValueOrDefault(), + rd), + CurrentOffset, + String("PROJCS"), + LeftDelimiterParser, + Map((name, geogcs, projection, parameters,unit,axes,extension,authority ) + => (name, geogcs, projection, parameters, unit, axes, extension, authority), + CsNameParser, + WktSeparatorParser.Then(GeographicCsParser(ctx)), + WktSeparatorParser.Then(ProjectionParser(ctx)), + Try(WktSeparatorParser.Then(ProjectionParameterParser(ctx))).Many(), + Try(WktSeparatorParser.Then(UnitParser(ctx))).Optional(), + Try(WktSeparatorParser.Then(AxisParser(ctx))).Many(), + Try(WktSeparatorParser.Then(ExtensionParser(ctx))).Optional(), + WktSeparatorParser.Then(AuthorityParser(ctx)).Optional()), + RightDelimiterParser) + .Labelled("WktProjectedCoordinateSystem"); + + + + public static Parser<char, object> SpatialReferenceSystemParser(Context ctx) + { + return OneOf( + Try(ProjectedCsParser(ctx)), + Try(GeographicCsParser(ctx)), + Try(GeocentricCsParser(ctx)), + Try(FittedCsParser(ctx)) + ).Labelled("WktCoordinateSystem"); + } + + + + public static Func<string, Result<char, T>> WktParserResolver<T>(IWktBuilder builder) + { + var ctx = new Context {builder = builder}; + return (str) => + { + using (var sr = new StringReader(str)) + { + return SpatialReferenceSystemParser(ctx).Cast<T>().Parse(sr); + } + }; + } + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/IWktAttribute.cs b/src/ProjNet.IO.Wkt/Tree/IWktAttribute.cs new file mode 100644 index 0000000..c7f64e3 --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/IWktAttribute.cs @@ -0,0 +1,14 @@ +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// Interface for specifying WKT Attributes. + /// </summary> + public interface IWktAttribute + { + /// <summary> + /// ToWKT. + /// </summary> + /// <returns></returns> + string ToWKT(); + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/IWktCrsObject.cs b/src/ProjNet.IO.Wkt/Tree/IWktCrsObject.cs new file mode 100644 index 0000000..8ecc7fc --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/IWktCrsObject.cs @@ -0,0 +1,10 @@ +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// Base interface for all WKT CRS Objects. + /// </summary> + public interface IWktCrsObject : IWktObject + { + + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/IWktObject.cs b/src/ProjNet.IO.Wkt/Tree/IWktObject.cs new file mode 100644 index 0000000..50b6ffd --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/IWktObject.cs @@ -0,0 +1,33 @@ +using ProjNet.IO.Wkt.Core; + +namespace ProjNet.IO.Wkt.Tree +{ + + /// <summary> + /// Base interface for all WKT Objects + /// </summary> + public interface IWktObject + { + /// <summary> + /// The Keyword as set and used by the WktParser. + /// </summary> + string Keyword { get; } + + + + /// <summary> + /// Traverse this object and its descendants calling the handler as we go down. + /// </summary> + /// <param name="handler"></param> + void Traverse(IWktTraverseHandler handler); + + + /// <summary> + /// Ouput this WktObject to string using the provided formatter. + /// If formatter is null the DefaultWktOutputFormatter is used. + /// </summary> + /// <param name="formatter"></param> + /// <returns></returns> + string ToString(IWktOutputFormatter formatter); + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktAngularUnit.cs b/src/ProjNet.IO.Wkt/Tree/WktAngularUnit.cs new file mode 100644 index 0000000..7f23419 --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktAngularUnit.cs @@ -0,0 +1,24 @@ +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// WktAngularUnit class. + /// </summary> + public class WktAngularUnit : WktUnit + { + /// <summary> + /// Constructor for WKT AngularUnit. + /// </summary> + /// <param name="name"></param> + /// <param name="conversionFactor"></param> + /// <param name="authority"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="rightDelimiter"></param> + public WktAngularUnit(string name, double conversionFactor, WktAuthority authority, + string keyword = "UNIT", char leftDelimiter = '[', char rightDelimiter = ']') + : base(name, conversionFactor, authority, keyword, leftDelimiter, rightDelimiter) + { + } + + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktAreaDescription.cs b/src/ProjNet.IO.Wkt/Tree/WktAreaDescription.cs new file mode 100644 index 0000000..b33186d --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktAreaDescription.cs @@ -0,0 +1,34 @@ +using System.Globalization; +using System.Text; + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// Area description is an optional attribute which describes a geographic area over which a CRS or coordinate operation is applicable. + /// </summary> + /// <remarks> + /// See 7.3.3.2 Area description in specification document. + /// </remarks> + public class AreaDescription : WktExtent + { + /// <summary> + /// The Text Description. + /// </summary> + public string Description { get; set; } + + /// <summary> + /// AreaDescriptionParser to WKT. + /// </summary> + /// <returns></returns> + public override string ToWKT() + { + var sb = new StringBuilder(); + + sb.Append("AREA[\""); + sb.Append(Description); + sb.Append("\"]"); + + return sb.ToString(); + } + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktAuthority.cs b/src/ProjNet.IO.Wkt/Tree/WktAuthority.cs new file mode 100644 index 0000000..c3ae051 --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktAuthority.cs @@ -0,0 +1,119 @@ +using ProjNet.IO.Wkt.Core; +using System; +using System.Globalization; +using System.Linq; +using System.Text; + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// WktAuthority - Simple POCO for Authority info. + /// </summary> + public class WktAuthority : WktBaseObject, IEquatable<WktAuthority> + { + /// <summary> + /// Name of this Authority. + /// </summary> + public string Name { get; set; } + + /// <summary> + /// Direction for this Authority. + /// </summary> + public long Code { get; internal set; } + + + /// <summary> + /// Constructor for WktAuthority. + /// </summary> + /// <param name="name"></param> + /// <param name="code"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="rightDelimiter"></param> + public WktAuthority(string name = null, string code = null, + string keyword = "AUTHORITY", char leftDelimiter = '[', char rightDelimiter = ']' ) + : base(keyword, leftDelimiter, rightDelimiter) + { + Name = name; + SetCode(code); + } + + /// <summary> + /// Setter for Code. + /// </summary> + /// <param name="code"></param> + /// <returns></returns> + public WktAuthority SetCode(string code) + { + if (!string.IsNullOrWhiteSpace(code)) + { + Code = long.Parse(new string(code.Where(c => char.IsDigit(c)).ToArray())); + } + + return this; + } + + /// <summary> + /// IEquatable.Equals implementation. + /// </summary> + /// <param name="other"></param> + /// <returns></returns> + public bool Equals(WktAuthority other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Name == other.Name && Code == other.Code; + } + + /// <summary> + /// Basic Equals override. + /// </summary> + /// <param name="obj"></param> + /// <returns></returns> + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((WktAuthority) obj); + } + + /// <summary> + /// Basic GetHashCode override. + /// </summary> + /// <returns></returns> + public override int GetHashCode() + { + unchecked + { + return ((Name != null ? Name.GetHashCode() : 0) * 397) ^ Code.GetHashCode(); + } + } + + /// <inheritdoc/>> + public override void Traverse(IWktTraverseHandler handler) + { + handler.Handle(this); + } + + /// <inheritdoc/> + public override string ToString(IWktOutputFormatter formatter) + { + formatter = formatter ?? new DefaultWktOutputFormatter(); + + var result = new StringBuilder(); + + formatter + .AppendKeyword(Keyword, result, false) + .AppendLeftDelimiter(LeftDelimiter, result) + .AppendQuotedText(Name, result) + .AppendSeparator(result, keepInside: true) + .AppendExtraWhitespace(result) + .AppendQuotedText(Code.ToString(CultureInfo.InvariantCulture), result) + .AppendRightDelimiter(RightDelimiter, result); + + return result.ToString(); + } + + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktAuthorityCitation.cs b/src/ProjNet.IO.Wkt/Tree/WktAuthorityCitation.cs new file mode 100644 index 0000000..d80fe21 --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktAuthorityCitation.cs @@ -0,0 +1,45 @@ +using System.Text; + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// AuthorityCitation a simple wkt helper class containing a (sub) Attribute. + /// </summary> + public class WktAuthorityCitation: IWktAttribute + { + private string Citation { get; set; } + + /// <summary> + /// AuthorityCitation + /// </summary> + /// <param name="citation"></param> + public WktAuthorityCitation(string citation) + { + Citation = citation; + } + + /// <summary> + /// Convert (back) to WKT. + /// </summary> + /// <returns></returns> + public string ToWKT() + { + var sb = new StringBuilder(); + + sb.Append($@"CITATION["""); + sb.Append(Citation); + sb.Append($@"""]"); + + return sb.ToString(); + } + + /// <summary> + /// ToString basic override. + /// </summary> + /// <returns></returns> + public override string ToString() + { + return Citation; + } + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktAxis.cs b/src/ProjNet.IO.Wkt/Tree/WktAxis.cs new file mode 100644 index 0000000..d73cdd4 --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktAxis.cs @@ -0,0 +1,121 @@ +using ProjNet.IO.Wkt.Core; +using System; +using System.Text; + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// WktAxis - Simple POCO for Axis info. + /// </summary> + public class WktAxis : WktBaseObject, IEquatable<WktAxis> + { + /// <summary> + /// Constructor using string for direction. + /// </summary> + /// <param name="name"></param> + /// <param name="direction"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="rightDelimiter"></param> + public WktAxis(string name, string direction, + string keyword = "AXIS", char leftDelimiter = '[', char rightDelimiter = ']') + : base(keyword, leftDelimiter, rightDelimiter) + { + Name = name; + SetDirection(direction); + } + + + /// <summary> + /// Name of this Axis. + /// </summary> + public string Name { get; set; } + + /// <summary> + /// Direction for this Axis. + /// </summary> + public WktAxisDirectionEnum Direction { get; set; } + + + + + + /// <summary> + /// Direction Setter method. + /// </summary> + /// <param name="direction"></param> + /// <returns></returns> + public WktAxis SetDirection(string direction) + { + if (!string.IsNullOrWhiteSpace(direction)) + { + Direction = (WktAxisDirectionEnum)Enum.Parse(typeof(WktAxisDirectionEnum), direction, true); + } + + return this; + } + + /// <summary> + /// IEquatable.Equals implementation for checking the whole tree except keywords and delimiters. + /// </summary> + /// <param name="other"></param> + /// <returns></returns> + public bool Equals(WktAxis other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Name == other.Name && Direction == other.Direction; + } + + /// <summary> + /// Basic override of Equals. + /// </summary> + /// <param name="obj"></param> + /// <returns></returns> + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((WktAxis) obj); + } + + /// <summary> + /// Basic override of GetHashCode. + /// </summary> + /// <returns></returns> + public override int GetHashCode() + { + unchecked + { + return ((Name != null ? Name.GetHashCode() : 0) * 397) ^ (int) Direction; + } + } + + + /// <inheritdoc/>> + public override void Traverse(IWktTraverseHandler handler) + { + handler.Handle(this); + } + + /// <inheritdoc/> + public override string ToString(IWktOutputFormatter formatter) + { + formatter = formatter ?? new DefaultWktOutputFormatter(); + + var result = new StringBuilder(); + + formatter + .AppendKeyword(Keyword, result) + .AppendLeftDelimiter(LeftDelimiter, result) + .AppendQuotedText(Name, result) + .AppendSeparator(result, keepInside: true) + .AppendExtraWhitespace(result) + .Append(Direction.ToString(), result) + .AppendRightDelimiter(RightDelimiter, result); + + return result.ToString(); + } + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktAxisDirectionEnum.cs b/src/ProjNet.IO.Wkt/Tree/WktAxisDirectionEnum.cs new file mode 100644 index 0000000..efd113b --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktAxisDirectionEnum.cs @@ -0,0 +1,44 @@ +namespace ProjNet.IO.Wkt.Tree +{ + + /// <summary> + /// WktAxisDirectionEnum. + /// </summary> + public enum WktAxisDirectionEnum + { + /// <summary> + /// Unknown or unspecified axis direction. This can be used for local or fitted coordinate systems. + /// </summary> + Other = 0, + + /// <summary> + /// Increasing ordinates values go North. This is usually used for Grid Y coordinates and Latitude. + /// </summary> + North = 1, + + /// <summary> + /// Increasing ordinates values go South. This is rarely used. + /// </summary> + South = 2, + + /// <summary> + /// Increasing ordinates values go East. This is rarely used. + /// </summary> + East = 3, + + /// <summary> + /// Increasing ordinates values go West. This is usually used for Grid X coordinates and Longitude. + /// </summary> + West = 4, + + /// <summary> + /// Increasing ordinates values go up. This is used for vertical coordinate systems. + /// </summary> + Up = 5, + + /// <summary> + /// Increasing ordinates values go down. This is used for vertical coordinate systems. + /// </summary> + Down = 6 + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktBBox.cs b/src/ProjNet.IO.Wkt/Tree/WktBBox.cs new file mode 100644 index 0000000..277d2ab --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktBBox.cs @@ -0,0 +1,56 @@ +using System.Globalization; +using System.Text; + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// BoundingBox is an optional WKT attribute. + /// </summary> + /// <remarks> + /// See 7.3.3.3 Geographic bounding box in specification document. + /// </remarks> + public class BBox : WktExtent + { + /// <summary> + /// The lower left latitude (Ymin) + /// </summary> + public double LowerLeftLatitude { get; set; } + + /// <summary> + /// The lower left longitude (Xmin) + /// </summary> + public double LowerLeftLongitude { get; set; } + + /// <summary> + /// The upper right latitude (Ymax) + /// </summary> + public double UpperRightLatitude { get; set; } + + /// <summary> + /// The upper right longitude (Xmax) + /// </summary> + public double UpperRightLongitude { get; set; } + + + /// <summary> + /// BBox to WKT. + /// </summary> + /// <returns></returns> + public override string ToWKT() + { + var sb = new StringBuilder(); + + sb.Append("BBOX["); + sb.Append(LowerLeftLatitude.ToString(CultureInfo.InvariantCulture)); + sb.Append(","); + sb.Append(LowerLeftLongitude.ToString(CultureInfo.InvariantCulture)); + sb.Append(","); + sb.Append(UpperRightLatitude.ToString(CultureInfo.InvariantCulture)); + sb.Append(","); + sb.Append(UpperRightLongitude.ToString(CultureInfo.InvariantCulture)); + sb.Append("]"); + + return sb.ToString(); + } + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktBaseObject.cs b/src/ProjNet.IO.Wkt/Tree/WktBaseObject.cs new file mode 100644 index 0000000..eeb1df2 --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktBaseObject.cs @@ -0,0 +1,69 @@ +using System; +using ProjNet.IO.Wkt.Core; + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// Base class for all WKT Objects. + /// </summary> + public abstract class WktBaseObject : IWktObject + { + /// <inheritdoc/> + public string Keyword { get; internal set; } + + /// <summary> + /// LeftDelimiter used after keyword to surround content. + /// </summary> + public char LeftDelimiter { get; set; } + + /// <summary> + /// RightDelimiter used after content. + /// </summary> + public char RightDelimiter { get; set; } + + + /// <summary> + /// Constructor for all Wkt Object's. + /// </summary> + /// <param name="keyword"></param> + /// <param name="left"></param> + /// <param name="right"></param> + /// <exception cref="NotSupportedException"></exception> + public WktBaseObject(string keyword = null, char left = '[', char right = ']') + { + Keyword = keyword; + + // Check the provided delimiters and store them. + if (!((left == '[' && right == ']') || (left == '(' && right == ')'))) + throw new NotSupportedException( + "Delimiters are not supported or left and right delimiters don't match!"); + + LeftDelimiter = left; + RightDelimiter = right; + } + + + /// <inheritdoc/> + public abstract void Traverse(IWktTraverseHandler handler); + + + /// <inheritdoc/> + public abstract string ToString(IWktOutputFormatter formatter); + /* + { + return Keyword; + } + */ + + + /// <summary> + /// Override default ToString calling the formatter version with a DefaultWktOutputFormatter. + /// </summary> + /// <returns></returns> + public override string ToString() + { + return this.ToString(new DefaultWktOutputFormatter()); + } + + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktCoordinateSystem.cs b/src/ProjNet.IO.Wkt/Tree/WktCoordinateSystem.cs new file mode 100644 index 0000000..462bb84 --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktCoordinateSystem.cs @@ -0,0 +1,60 @@ +using ProjNet.IO.Wkt.Core; +using System; + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// Base class for all WktCoordinateSystem classes. + /// </summary> + public class WktCoordinateSystem : WktBaseObject + { + + /// <summary> + /// Name property. + /// </summary> + public string Name { get; internal set; } + + + + /// <summary> + /// Constructor for CoordinateSystem. + /// </summary> + /// <param name="name"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="rightDelimiter"></param> + public WktCoordinateSystem(string name, + string keyword, char leftDelimiter = '[', char rightDelimiter = ']') + : base(keyword, leftDelimiter, rightDelimiter) + { + Name = name; + } + + /// <inheritdoc/>> + public override void Traverse(IWktTraverseHandler handler) + { + if (this is WktProjectedCoordinateSystem projcs) + projcs.Traverse(handler); + else if (this is WktGeographicCoordinateSystem geogcs) + geogcs.Traverse(handler); + else if (this is WktGeocentricCoordinateSystem geoccs) + geoccs.Traverse(handler); + } + + + /// <inheritdoc/> + public override string ToString(IWktOutputFormatter formatter) + { + formatter = formatter ?? new DefaultWktOutputFormatter(); + + if (this is WktProjectedCoordinateSystem projcs) + return projcs.ToString(formatter); + else if (this is WktGeographicCoordinateSystem geogcs) + return geogcs.ToString(formatter); + else if (this is WktGeocentricCoordinateSystem geoccs) + return geoccs.ToString(formatter); + + return Keyword; + } + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktDatum.cs b/src/ProjNet.IO.Wkt/Tree/WktDatum.cs new file mode 100644 index 0000000..d64ac95 --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktDatum.cs @@ -0,0 +1,146 @@ +using ProjNet.IO.Wkt.Core; +using System; +using System.Text; + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// WktDatum class. + /// </summary> + public class WktDatum : WktBaseObject, IEquatable<WktDatum> + { + /// <summary> + /// Name property. + /// </summary> + public string Name { get; internal set; } + + /// <summary> + /// Spheroid property. + /// </summary> + public WktSpheroid Spheroid { get; internal set; } + + /// <summary> + /// ToWgs84 property. + /// </summary> + public WktToWgs84 ToWgs84 { get; internal set; } + + /// <summary> + /// Authority property. + /// </summary> + public WktAuthority Authority { get; internal set; } + + + /// <summary> + /// Constructor for WktDatum. + /// </summary> + /// <param name="name"></param> + /// <param name="spheroid"></param> + /// <param name="toWgs84"></param> + /// <param name="authority"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="rightDelimiter"></param> + public WktDatum(string name, WktSpheroid spheroid, WktToWgs84 toWgs84, WktAuthority authority, + string keyword = "DATUM", char leftDelimiter = '[', char rightDelimiter = ']') + : base(keyword, leftDelimiter, rightDelimiter) + { + Name = name; + Spheroid = spheroid; + ToWgs84 = toWgs84; + Authority = authority; + } + + + /// <summary> + /// IEquatable.Equals implementation checking the whole tree. + /// </summary> + /// <param name="other"></param> + /// <returns></returns> + public bool Equals(WktDatum other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Name == other.Name && Equals(Spheroid, other.Spheroid) && Equals(ToWgs84, other.ToWgs84) && Equals(Authority, other.Authority); + } + + /// <summary> + /// Override basic Equals method. + /// </summary> + /// <param name="obj"></param> + /// <returns></returns> + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((WktDatum) obj); + } + + /// <summary> + /// Override basic GetHashCode method. + /// </summary> + /// <returns></returns> + public override int GetHashCode() + { + unchecked + { + int hashCode = (Name != null ? Name.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Spheroid != null ? Spheroid.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (ToWgs84 != null ? ToWgs84.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Authority != null ? Authority.GetHashCode() : 0); + return hashCode; + } + } + + /// <inheritdoc/> + public override void Traverse(IWktTraverseHandler handler) + { + if (Spheroid!=null) + this.Spheroid.Traverse(handler); + if (ToWgs84!=null) + this.ToWgs84.Traverse(handler); + if (Authority!=null) + this.Authority.Traverse(handler); + + handler.Handle(this); + } + + /// <inheritdoc/> + public override string ToString(IWktOutputFormatter formatter) + { + formatter = formatter ?? new DefaultWktOutputFormatter(); + + var result = new StringBuilder(); + + formatter + .AppendKeyword(Keyword, result) + .AppendLeftDelimiter(LeftDelimiter, result) + .AppendQuotedText(Name, result) + .AppendSeparator(result) + .AppendExtraWhitespace(result) + .Append(Spheroid.ToString(formatter), result); + + if (ToWgs84 != null) + { + formatter + .AppendSeparator(result) + .AppendExtraWhitespace(result) + .Append(ToWgs84.ToString(formatter), result); + } + + if (Authority != null) + { + formatter + .AppendSeparator(result, keepInside: false) + .AppendIndent(result) + .Append(Authority.ToString(formatter), result); + } + + formatter + .AppendRightDelimiter(RightDelimiter, result); + + return result.ToString(); + } + + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktEllipsoid.cs b/src/ProjNet.IO.Wkt/Tree/WktEllipsoid.cs new file mode 100644 index 0000000..19b806f --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktEllipsoid.cs @@ -0,0 +1,135 @@ +using ProjNet.IO.Wkt.Core; +using System; +using System.Text; + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// WktEllipsoid class. + /// </summary> + public class WktEllipsoid : WktBaseObject, IEquatable<WktEllipsoid> + { + /// <summary> + /// Name property. + /// </summary> + public string Name { get; internal set; } + /// <summary> + /// SemiMajorAxis property. + /// </summary> + public double SemiMajorAxis { get; internal set; } + /// <summary> + /// InverseFlattening property. + /// </summary> + public double InverseFlattening { get; internal set; } + /// <summary> + /// Authority property. + /// </summary> + public WktAuthority Authority { get; internal set; } + + /// <summary> + /// Constructor for a WktEllipsoid. + /// </summary> + /// <param name="name"></param> + /// <param name="semiMajorAxis"></param> + /// <param name="inverseFlattening"></param> + /// <param name="authority"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="rightDelimiter"></param> + public WktEllipsoid(string name, double semiMajorAxis, double inverseFlattening, WktAuthority authority, + string keyword = "ELLIPSOID", char leftDelimiter = '[', char rightDelimiter = ']') + : base(keyword, leftDelimiter, rightDelimiter) + { + Name = name; + SemiMajorAxis = semiMajorAxis; + InverseFlattening = inverseFlattening; + Authority = authority; + } + + + + /// <summary> + /// IEquatable.Equals implementation checking the whole tree. + /// </summary> + /// <param name="other"></param> + /// <returns></returns> + public bool Equals(WktEllipsoid other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Name == other.Name && SemiMajorAxis.Equals(other.SemiMajorAxis) && InverseFlattening.Equals(other.InverseFlattening) && Equals(Authority, other.Authority); + } + + /// <summary> + /// Override of basic Equals. + /// </summary> + /// <param name="obj"></param> + /// <returns></returns> + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((WktEllipsoid) obj); + } + + /// <summary> + /// Override of basic GetHashCode. + /// </summary> + /// <returns></returns> + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ (Name != null ? Name.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ SemiMajorAxis.GetHashCode(); + hashCode = (hashCode * 397) ^ InverseFlattening.GetHashCode(); + hashCode = (hashCode * 397) ^ (Authority != null ? Authority.GetHashCode() : 0); + return hashCode; + } + } + + + /// <inheritdoc/> + public override void Traverse(IWktTraverseHandler handler) + { + if (Authority!=null) + Authority.Traverse(handler); + + handler.Handle(this); + } + + /// <inheritdoc/> + public override string ToString(IWktOutputFormatter formatter) + { + formatter = formatter ?? new DefaultWktOutputFormatter(); + + var result = new StringBuilder(); + + formatter + .AppendKeyword(Keyword, result) + .AppendLeftDelimiter(LeftDelimiter, result) + .AppendQuotedText(Name, result) + .AppendSeparator(result, keepInside: true) + .AppendExtraWhitespace(result) + .Append(SemiMajorAxis, result) + .AppendSeparator(result, keepInside: true) + .AppendExtraWhitespace(result) + .Append(InverseFlattening, result); + + if (Authority != null) + { + formatter + .AppendSeparator(result, keepInside: true) + .AppendExtraWhitespace(result) + .Append(Authority.ToString(formatter), result); + } + + formatter + .AppendRightDelimiter(RightDelimiter, result); + + return result.ToString(); + } + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktExtension.cs b/src/ProjNet.IO.Wkt/Tree/WktExtension.cs new file mode 100644 index 0000000..329def9 --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktExtension.cs @@ -0,0 +1,103 @@ +using ProjNet.IO.Wkt.Core; +using System; +using System.Text; + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// WktExtension + /// </summary> + public class WktExtension : WktBaseObject, IEquatable<WktExtension> + { + /// <summary> + /// Name of this Extension. + /// </summary> + public string Name { get; set; } + + /// <summary> + /// Direction for this Authority. + /// </summary> + public string Value { get; set; } + + + /// <summary> + /// Constructor. + /// </summary> + /// <param name="name"></param> + /// <param name="text"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="rightDelimiter"></param> + public WktExtension(string name, string text, + string keyword = "EXTENSION", char leftDelimiter = '[', char rightDelimiter = ']') + : base(keyword, leftDelimiter, rightDelimiter) + { + Name = name; + Value = text; + } + + + /// <summary> + /// IEquatable.Equals implementation checking whole tree. + /// </summary> + /// <param name="other"></param> + /// <returns></returns> + public bool Equals(WktExtension other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Name == other.Name && Value == other.Value; + } + + /// <summary> + /// Basic override for Equals. + /// </summary> + /// <param name="obj"></param> + /// <returns></returns> + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((WktExtension) obj); + } + + /// <summary> + /// Basic override of GetHashCode. + /// </summary> + /// <returns></returns> + public override int GetHashCode() + { + unchecked + { + return ((Name != null ? Name.GetHashCode() : 0) * 397) ^ Value.GetHashCode(); + } + } + + /// <inheritdoc/> + public override void Traverse(IWktTraverseHandler handler) + { + handler.Handle(this); + } + + /// <inheritdoc/> + public override string ToString(IWktOutputFormatter formatter) + { + formatter = formatter ?? new DefaultWktOutputFormatter(); + + var result = new StringBuilder(); + + formatter + .AppendKeyword(Keyword, result) + .AppendLeftDelimiter(LeftDelimiter, result) + .AppendQuotedText(Name, result) + .AppendSeparator(result, keepInside: true) + .AppendExtraWhitespace(result) + .AppendQuotedText(Value, result) + .AppendRightDelimiter(RightDelimiter, result); + + return result.ToString(); + } + + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktExtent.cs b/src/ProjNet.IO.Wkt/Tree/WktExtent.cs new file mode 100644 index 0000000..6c8d968 --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktExtent.cs @@ -0,0 +1,15 @@ +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// Extent attribute + /// </summary> + public abstract class WktExtent : IWktAttribute + { + /// <summary> + /// Convert (back) to WKT. + /// </summary> + /// <returns></returns> + public abstract string ToWKT(); + + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktFittedCoordinateSystem.cs b/src/ProjNet.IO.Wkt/Tree/WktFittedCoordinateSystem.cs new file mode 100644 index 0000000..af48dae --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktFittedCoordinateSystem.cs @@ -0,0 +1,95 @@ +using ProjNet.IO.Wkt.Core; +using System.Text; + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// WktFittedCoordinateSystem class. + /// </summary> + public class WktFittedCoordinateSystem : WktCoordinateSystem + { + + /// <summary> + /// ParameterMathTransform property. + /// </summary> + public WktParameterMathTransform ParameterMathTransform { get; internal set; } + + /// <summary> + /// ProjectedCoordinateSystem property. + /// </summary> + public WktProjectedCoordinateSystem ProjectedCoordinateSystem { get; internal set; } + + /// <summary> + /// Authority property. + /// </summary> + public WktAuthority Authority { get; internal set; } + + + /// <summary> + /// Constructor. + /// </summary> + /// <param name="name"></param> + /// <param name="pmt"></param> + /// <param name="projcs"></param> + /// <param name="authority"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="rightDelimiter"></param> + public WktFittedCoordinateSystem(string name, WktParameterMathTransform pmt, WktProjectedCoordinateSystem projcs, WktAuthority authority, + string keyword, char leftDelimiter = '[', char rightDelimiter = ']') + : base(name, keyword, leftDelimiter, rightDelimiter) + { + Name = name; + ParameterMathTransform = pmt; + ProjectedCoordinateSystem = projcs; + Authority = authority; + } + + /// <summary> + /// Traverse method for WktFittedCoordinateSystem. + /// </summary> + /// <param name="handler"></param> + public override void Traverse(IWktTraverseHandler handler) + { + ParameterMathTransform.Traverse(handler); + ProjectedCoordinateSystem.Traverse(handler); + if(Authority!=null) + Authority.Traverse(handler); + handler.Handle(this); + } + + /// <summary> + /// ToString implementation with optional OutputFormatter support. + /// </summary> + /// <param name="formatter"></param> + /// <returns></returns> + public override string ToString(IWktOutputFormatter formatter) + { + formatter = formatter ?? new DefaultWktOutputFormatter(); + + var result = new StringBuilder(); + + formatter + .AppendKeyword(Keyword, result) + .AppendLeftDelimiter(LeftDelimiter, result) + .AppendQuotedText(Name, result) + .AppendSeparator(result) + .Append(ParameterMathTransform.ToString(formatter), result) + .AppendSeparator(result) + .Append(ProjectedCoordinateSystem.ToString(formatter), result); + + if (Authority != null) + { + formatter + .AppendSeparator(result) + .Append(Authority.ToString(formatter), result); + } + + formatter + .AppendRightDelimiter(RightDelimiter, result); + + return result.ToString(); + } + + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktGeocentricCoordinateSystem.cs b/src/ProjNet.IO.Wkt/Tree/WktGeocentricCoordinateSystem.cs new file mode 100644 index 0000000..8e5ec8f --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktGeocentricCoordinateSystem.cs @@ -0,0 +1,172 @@ +using ProjNet.IO.Wkt.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// WktGeocentricCoordinateSystem class. + /// </summary> + public class WktGeocentricCoordinateSystem : WktCoordinateSystem, IEquatable<WktGeocentricCoordinateSystem> + { + /// <summary> + /// Datum property. + /// </summary> + public WktDatum Datum { get; internal set; } + + /// <summary> + /// PrimeMeridian property. + /// </summary> + public WktPrimeMeridian PrimeMeridian { get; internal set; } + + /// <summary> + /// Unit property. + /// </summary> + public WktUnit Unit { get; internal set; } + + /// <summary> + /// Authority property. + /// </summary> + public WktAuthority Authority { get; internal set; } + + /// <summary> + /// Axes property. + /// </summary> + public IEnumerable<WktAxis> Axes { get; internal set; } + + + /// <summary> + /// Constructor. + /// </summary> + /// <param name="name"></param> + /// <param name="datum"></param> + /// <param name="meridian"></param> + /// <param name="unit"></param> + /// <param name="axes"></param> + /// <param name="authority"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="rightDelimiter"></param> + public WktGeocentricCoordinateSystem(string name, WktDatum datum, WktPrimeMeridian meridian, + WktUnit unit, IEnumerable<WktAxis> axes, WktAuthority authority, + string keyword = "GEOCS", char leftDelimiter = '[', char rightDelimiter = ']') + : base(name, keyword, leftDelimiter, rightDelimiter) + { + Datum = datum; + PrimeMeridian = meridian; + Unit = unit; + Axes = axes; + Authority = authority; + } + + /// <summary> + /// Implementation of IEquatable.Equals. + /// </summary> + /// <param name="other"></param> + /// <returns></returns> + public bool Equals(WktGeocentricCoordinateSystem other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(Datum, other.Datum) && Equals(PrimeMeridian, other.PrimeMeridian) && Equals(Unit, other.Unit) && Equals(Authority, other.Authority) && Axes.SequenceEqual(other.Axes); + } + + /// <summary> + /// Override of basic Equals. + /// </summary> + /// <param name="obj"></param> + /// <returns></returns> + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((WktGeocentricCoordinateSystem) obj); + } + + /// <summary> + /// Override of basic GetHashCode. + /// </summary> + /// <returns></returns> + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ (Datum != null ? Datum.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (PrimeMeridian != null ? PrimeMeridian.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Unit != null ? Unit.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Authority != null ? Authority.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Axes != null ? Axes.GetHashCode() : 0); + return hashCode; + } + } + + /// <inheritdoc/> + public override void Traverse(IWktTraverseHandler handler) + { + Datum.Traverse(handler); + PrimeMeridian.Traverse(handler); + Unit.Traverse(handler); + + if (Authority!=null) + Authority.Traverse(handler); + + foreach (var axis in Axes) + axis.Traverse(handler); + + handler.Handle(this); + } + + /// <inheritdoc/> + public override string ToString(IWktOutputFormatter formatter) + { + formatter = formatter ?? new DefaultWktOutputFormatter(); + + var result = new StringBuilder(); + + formatter + .AppendKeyword(Keyword, result) + .AppendLeftDelimiter(LeftDelimiter, result) + .AppendQuotedText(Name, result) + + .AppendSeparator(result) + .AppendExtraWhitespace(result) + .Append(Datum.ToString(formatter), result) + + .AppendSeparator(result) + .AppendExtraWhitespace(result) + .Append(PrimeMeridian.ToString(formatter), result) + + .AppendSeparator(result) + .AppendExtraWhitespace(result) + .Append(Unit.ToString(formatter), result); + + if (Axes != null && Axes.Any()) + { + foreach (var axis in Axes) + { + formatter + .AppendSeparator(result) + .AppendExtraWhitespace(result) + .Append(axis.ToString(formatter), result); + } + } + + if (Authority != null) + { + formatter + .AppendSeparator(result, keepInside: true) + .AppendExtraWhitespace(result) + .Append(Authority.ToString(formatter), result); + } + + formatter + .AppendRightDelimiter(RightDelimiter, result); + + return result.ToString(); + } + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktGeographicCoordinateSystem.cs b/src/ProjNet.IO.Wkt/Tree/WktGeographicCoordinateSystem.cs new file mode 100644 index 0000000..1670047 --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktGeographicCoordinateSystem.cs @@ -0,0 +1,222 @@ +using ProjNet.IO.Wkt.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// Builder class for GeographicCoordinateSystem + /// </summary> + public class WktGeographicCoordinateSystem : WktCoordinateSystem, IEquatable<WktGeographicCoordinateSystem> + { + /// <summary> + /// AngularUnit property. + /// </summary> + public WktUnit AngularUnit { get; set; } + + /// <summary> + /// Unit property. + /// </summary> + public WktUnit Unit { get; set; } + + /// <summary> + /// HorizontalDatum property. + /// </summary> + public WktDatum HorizontalDatum { get; set; } + + + /// <summary> + /// PrimeMeridian property. + /// </summary> + public WktPrimeMeridian PrimeMeridian { get; set; } + + + /// <summary> + /// Zero to Many Axes. + /// </summary> + public IEnumerable<WktAxis> Axes { get; set; } + + /// <summary> + /// WktAuthority property. (Optional) + /// </summary> + public WktAuthority Authority{ get; set; } + + /// <summary> + /// Alias property. + /// </summary> + public string Alias { get; set; } + + /// <summary> + /// Abbreviation property. + /// </summary> + public string Abbreviation { get; set; } + + /// <summary> + /// Remarks property. + /// </summary> + public string Remarks { get; set; } + + + /// <summary> + /// Constructor. + /// </summary> + public WktGeographicCoordinateSystem(string name, WktDatum datum, WktPrimeMeridian meridian, WktUnit angularUnit, + IEnumerable<WktAxis> axes, WktAuthority authority, + string keyword = "GEOGCS", char leftDelimiter = '[', char rightDelimiter = ']') + : base(name, keyword, leftDelimiter, rightDelimiter) + { + HorizontalDatum = datum; + PrimeMeridian = meridian; + AngularUnit = angularUnit; + Axes = axes; + Authority = authority; + } + + + /// <summary> + /// Implementing IEquatable.Equals. + /// </summary> + /// <param name="other"></param> + /// <returns></returns> + public bool Equals(WktGeographicCoordinateSystem other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(AngularUnit, other.AngularUnit) && + Equals(Unit, other.Unit) && + Equals(HorizontalDatum, other.HorizontalDatum) && + Equals(PrimeMeridian, other.PrimeMeridian) && + Axes.SequenceEqual(other.Axes) && + Equals(Authority, other.Authority) && + Alias == other.Alias && Abbreviation == other.Abbreviation && Remarks == other.Remarks; + } + + /// <summary> + /// Overriding of basic Equals. + /// </summary> + /// <param name="obj"></param> + /// <returns></returns> + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((WktGeographicCoordinateSystem) obj); + } + + /// <summary> + /// Overriding of basic GetHashCode. + /// </summary> + /// <returns></returns> + public override int GetHashCode() + { + unchecked + { + int hashCode = (AngularUnit != null ? AngularUnit.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Unit != null ? Unit.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (HorizontalDatum != null ? HorizontalDatum.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (PrimeMeridian != null ? PrimeMeridian.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Axes != null ? Axes.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Authority != null ? Authority.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Alias != null ? Alias.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Abbreviation != null ? Abbreviation.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Remarks != null ? Remarks.GetHashCode() : 0); + return hashCode; + } + } + + + /// <inheritdoc/> + public override void Traverse(IWktTraverseHandler handler) + { + if (AngularUnit!=null) + AngularUnit.Traverse(handler); + if (Unit!=null) + Unit.Traverse(handler); + if (HorizontalDatum!=null) + HorizontalDatum.Traverse(handler); + if (PrimeMeridian!=null) + PrimeMeridian.Traverse(handler); + + foreach (var axis in Axes) + if (axis!=null) + axis.Traverse(handler); + + if (Authority!=null) + Authority.Traverse(handler); + + handler.Handle(this); + } + + /// <inheritdoc/> + public override string ToString(IWktOutputFormatter formatter) + { + formatter = formatter ?? new DefaultWktOutputFormatter(); + + var result = new StringBuilder(); + + formatter + .AppendKeyword(Keyword, result) + .AppendLeftDelimiter(LeftDelimiter, result) + .AppendQuotedText(Name, result); + + if (HorizontalDatum != null) + { + formatter + .AppendSeparator(result) + .AppendExtraWhitespace(result) + .Append(HorizontalDatum.ToString(formatter), result); + } + + if (PrimeMeridian != null) + { + formatter + .AppendSeparator(result) + .AppendExtraWhitespace(result) + .Append(PrimeMeridian.ToString(formatter), result); + } + + if (AngularUnit != null) + { + formatter + .AppendSeparator(result) + .AppendExtraWhitespace(result) + .Append(AngularUnit.ToString(formatter), result); + } + + if (Unit != null) + { + formatter + .AppendSeparator(result) + .AppendExtraWhitespace(result) + .Append(Unit.ToString(formatter), result); + } + + if (Axes != null && Axes.Any()) + { + foreach (var axis in Axes) + { + formatter + .AppendSeparator(result) + .AppendExtraWhitespace(result) + .Append(axis.ToString(formatter), result); + } + } + + if (Authority != null) + { + formatter + .AppendSeparator(result, keepInside: false) + .AppendIndent(result) + .Append(Authority.ToString(formatter), result); + } + + formatter + .AppendRightDelimiter(RightDelimiter, result); + + return result.ToString(); + } + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktIdentifier.cs b/src/ProjNet.IO.Wkt/Tree/WktIdentifier.cs new file mode 100644 index 0000000..8775666 --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktIdentifier.cs @@ -0,0 +1,94 @@ + +using ProjNet.IO.Wkt.Core; +using System; +using System.Globalization; +using System.Text; + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// Identifier is an optional attribute which references an external + /// description of the object and which may be applied to a coordinate reference system, a coordinate + /// operation or a boundCRS. It may also be utilised for components of these objects although this + /// is not recommended except for coordinate operation methods (including map projections) + /// and parameters. Multiple identifiers may be given for any object. + /// + /// When an identifier is given for a coordinate reference system, coordinate operation or boundCRS, + /// it applies to the whole object including all of its components. + /// + /// Should any attributes or values given in the cited identifier be in conflict with attributes + /// or values given explicitly in the WKT description, the WKT values shall prevail. + /// </summary> + /// <remarks> + /// See 7.3.4 Identifier in specification document. + /// </remarks> + public class WktIdentifier : IWktAttribute + { + /// <summary> + /// AuthorityName + /// </summary> + public string AuthorityName { get; set; } + + /// <summary> + /// AuthorityUniqueIdentifier + /// </summary> + public object AuthorityUniqueIdentifier { get; set; } + + /// <summary> + /// Version (Optional) + /// </summary> + public object Version { get; set; } + + /// <summary> + /// AuthorityCitation (Optional) + /// </summary> + public WktAuthorityCitation AuthorityCitation { get; set; } + + /// <summary> + /// Id Uri (Optional) + /// </summary> + /// <returns></returns> + public WktUri IdUri { get; set; } + + + /// <summary> + /// ToWKT. + /// </summary> + /// <returns></returns> + public string ToWKT() + { + var sb = new StringBuilder(); + + sb.Append("ID["); + + sb.Append($@"""{AuthorityName}"""); + sb.Append($@"""{AuthorityUniqueIdentifier}"""); + + if (Version != null) + { + if (Version is double d) + { + sb.Append(d.ToString(CultureInfo.InvariantCulture)); + } + else if (Version is string vs) + { + sb.Append($@"""{vs}"""); + } + } + + if (AuthorityCitation != null) + { + sb.Append(AuthorityCitation.ToWKT()); + } + + if (IdUri != null) + { + sb.Append(IdUri.ToWKT()); + } + + sb.Append("]"); + + return sb.ToString(); + } + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktLinearUnit.cs b/src/ProjNet.IO.Wkt/Tree/WktLinearUnit.cs new file mode 100644 index 0000000..5615a45 --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktLinearUnit.cs @@ -0,0 +1,25 @@ + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// WktLinearUnit class. + /// </summary> + public class WktLinearUnit : WktUnit + { + /// <summary> + /// Constructor for WKT LinearUnit. + /// </summary> + /// <param name="name"></param> + /// <param name="conversionFactor"></param> + /// <param name="authority"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="rightDelimiter"></param> + public WktLinearUnit(string name, double conversionFactor, WktAuthority authority, + string keyword = "UNIT", char leftDelimiter = '[', char rightDelimiter = ']') + : base(name, conversionFactor, authority, keyword, leftDelimiter, rightDelimiter) + { + } + + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktParameter.cs b/src/ProjNet.IO.Wkt/Tree/WktParameter.cs new file mode 100644 index 0000000..c3f98c0 --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktParameter.cs @@ -0,0 +1,105 @@ +using ProjNet.IO.Wkt.Core; +using System; +using System.Text; + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// WktParameter + /// </summary> + public class WktParameter : WktBaseObject, IEquatable<WktParameter> + { + + /// <summary> + /// Name property. + /// </summary> + public string Name { get; internal set; } + /// <summary> + /// Value property. + /// </summary> + public double Value { get; internal set; } + + + /// <summary> + /// Constructor with optional name. + /// </summary> + /// <param name="name"></param> + /// <param name="value"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="rightDelimiter"></param> + public WktParameter(string name, double value, + string keyword = "PARAMETER", char leftDelimiter = '[', char rightDelimiter = ']') + : base(keyword, leftDelimiter, rightDelimiter) + { + Name = name; + Value = value; + } + + + + /// <summary> + /// IEquatable.Equals implementation checking the whole tree. + /// </summary> + /// <param name="other"></param> + /// <returns></returns> + public bool Equals(WktParameter other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Name == other.Name && Value.Equals(other.Value); + } + + /// <summary> + /// Override of basic Equals. + /// </summary> + /// <param name="obj"></param> + /// <returns></returns> + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((WktParameter) obj); + } + + /// <summary> + /// Override of basic GetHashCode. + /// </summary> + /// <returns></returns> + public override int GetHashCode() + { + unchecked + { + return ((Name != null ? Name.GetHashCode() : 0) * 397) ^ Value.GetHashCode(); + } + } + + + /// <inheritdoc/> + public override void Traverse(IWktTraverseHandler handler) + { + handler.Handle(this); + } + + + /// <inheritdoc/> + public override string ToString(IWktOutputFormatter formatter) + { + formatter = formatter ?? new DefaultWktOutputFormatter(); + + var result = new StringBuilder(); + + formatter + .AppendKeyword(Keyword, result) + .AppendLeftDelimiter(LeftDelimiter, result) + .AppendQuotedText(Name, result) + .AppendSeparator(result, keepInside: true) + .AppendExtraWhitespace(result) + .Append(Value, result) + .AppendRightDelimiter(RightDelimiter, result); + + return result.ToString(); + } + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktParameterMathTransform.cs b/src/ProjNet.IO.Wkt/Tree/WktParameterMathTransform.cs new file mode 100644 index 0000000..1408310 --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktParameterMathTransform.cs @@ -0,0 +1,119 @@ +using ProjNet.IO.Wkt.Core; +using System; +using System.Collections.Generic; +using System.Text; + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// WktParameterMathTransform for WktFittedCoordinateSystem. + /// </summary> + public class WktParameterMathTransform : WktBaseObject, IEquatable<WktParameterMathTransform> + { + /// <summary> + /// Name property. + /// </summary> + public string Name { get; internal set; } + + /// <summary> + /// Parameters property. + /// </summary> + public IEnumerable<WktParameter> Parameters { get; internal set; } + + + /// <summary> + /// Constructor. + /// </summary> + /// <param name="name"></param> + /// <param name="parameters"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="rightDelimiter"></param> + public WktParameterMathTransform(string name, IEnumerable<WktParameter> parameters, + string keyword = "PARAM_MT", char leftDelimiter = '[', char rightDelimiter = ']') + : base(keyword, leftDelimiter, rightDelimiter) + { + Name = name; + Parameters = parameters; + } + + + /// <summary> + /// Traverse method for this WktParametersMathTransform. + /// </summary> + /// <param name="handler"></param> + public override void Traverse(IWktTraverseHandler handler) + { + foreach (var p in Parameters) + { + p.Traverse(handler); + } + + handler.Handle(this); + } + + /// <summary> + /// ToString method with optional Outputformatter support. + /// </summary> + /// <param name="formatter"></param> + /// <returns></returns> + public override string ToString(IWktOutputFormatter formatter) + { + formatter = formatter ?? new DefaultWktOutputFormatter(); + + var result = new StringBuilder(); + + formatter + .AppendKeyword(Keyword, result) + .AppendLeftDelimiter(LeftDelimiter, result) + .AppendQuotedText(Name, result); + + foreach (var p in Parameters) + { + formatter + .AppendSeparator(result) + .Append(p.ToString(formatter), result); + } + + formatter + .AppendRightDelimiter(RightDelimiter, result); + + + return result.ToString(); + } + + /// <summary> + /// IEquatble.Equals implementation. + /// </summary> + /// <param name="other"></param> + /// <returns></returns> + public bool Equals(WktParameterMathTransform other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Name == other.Name; + } + + /// <summary> + /// Override of bsic Equals. + /// </summary> + /// <param name="obj"></param> + /// <returns></returns> + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((WktParameterMathTransform) obj); + } + + /// <summary> + /// Override of basic GetHashCode. + /// </summary> + /// <returns></returns> + public override int GetHashCode() + { + return (Name != null ? Name.GetHashCode() : 0); + } + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktPrimeMeridian.cs b/src/ProjNet.IO.Wkt/Tree/WktPrimeMeridian.cs new file mode 100644 index 0000000..2e5fecf --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktPrimeMeridian.cs @@ -0,0 +1,127 @@ +using ProjNet.IO.Wkt.Core; +using System; +using System.Text; + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// WktPrimeMeridian property. + /// </summary> + public class WktPrimeMeridian : WktBaseObject, IEquatable<WktPrimeMeridian> + { + + /// <summary> + /// Name property. + /// </summary> + public string Name { get; internal set; } + + /// <summary> + /// Longitude property. + /// </summary> + public double Longitude { get; internal set; } + + /// <summary> + /// Authority property. + /// </summary> + public WktAuthority Authority { get; internal set; } + + + /// <summary> + /// Constructor for the WktPrimeMeridian class. + /// </summary> + /// <param name="name"></param> + /// <param name="longitude"></param> + /// <param name="authority"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="rightDelimiter"></param> + public WktPrimeMeridian(string name, double longitude, WktAuthority authority, + string keyword = "PRIMEM", char leftDelimiter = '[', char rightDelimiter = ']') + : base(keyword, leftDelimiter, rightDelimiter) + { + Name = name; + Longitude = longitude; + Authority = authority; + } + + /// <summary> + /// Implements IEquatable.Equals checking the whole tree. + /// </summary> + /// <param name="other"></param> + /// <returns></returns> + public bool Equals(WktPrimeMeridian other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Name == other.Name && Longitude.Equals(other.Longitude) && Equals(Authority, other.Authority); + } + + /// <summary> + /// Overriding base Equals method. + /// </summary> + /// <param name="obj"></param> + /// <returns></returns> + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((WktPrimeMeridian) obj); + } + + /// <summary> + /// Overriding base GetHashCode method. + /// </summary> + /// <returns></returns> + public override int GetHashCode() + { + unchecked + { + int hashCode = (Name != null ? Name.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ Longitude.GetHashCode(); + hashCode = (hashCode * 397) ^ (Authority != null ? Authority.GetHashCode() : 0); + return hashCode; + } + } + + + /// <inheritdoc/> + public override void Traverse(IWktTraverseHandler handler) + { + if (Authority!=null) + Authority.Traverse(handler); + + handler.Handle(this); + } + + /// <inheritdoc/> + public override string ToString(IWktOutputFormatter formatter) + { + formatter = formatter ?? new DefaultWktOutputFormatter(); + + var result = new StringBuilder(); + + formatter + .AppendKeyword(Keyword, result) + .AppendLeftDelimiter(LeftDelimiter, result) + .AppendQuotedText(Name, result) + .AppendSeparator(result, keepInside: true) + .AppendExtraWhitespace(result) + .Append(Longitude, result); + + + if (Authority != null) + { + formatter + .AppendSeparator(result, keepInside: true) + .AppendExtraWhitespace(result) + .Append(Authority.ToString(formatter), result); + } + + formatter + .AppendRightDelimiter(RightDelimiter, result); + + return result.ToString(); + } + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktProjectedCoordinateSystem.cs b/src/ProjNet.IO.Wkt/Tree/WktProjectedCoordinateSystem.cs new file mode 100644 index 0000000..56741b2 --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktProjectedCoordinateSystem.cs @@ -0,0 +1,227 @@ +using ProjNet.IO.Wkt.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// WktProjectedCoordinateSystem class. + /// </summary> + public class WktProjectedCoordinateSystem : WktCoordinateSystem, IEquatable<WktProjectedCoordinateSystem> + { + /// <summary> + /// GeographicCoordinateSystem property. + /// </summary> + public WktGeographicCoordinateSystem GeographicCoordinateSystem { get; internal set; } + + /// <summary> + /// Projection property. + /// </summary> + public WktProjection Projection { get; internal set; } + + /// <summary> + /// Parameters property. + /// </summary> + public IEnumerable<WktParameter> Parameters { get; internal set; } + + /// <summary> + /// Unit property. + /// </summary> + public WktUnit Unit { get; internal set; } + + /// <summary> + /// Axes property. + /// </summary> + public IEnumerable<WktAxis> Axes { get; internal set; } + + /// <summary> + /// Authority property. + /// </summary> + public WktAuthority Authority { get; internal set; } + + /// <summary> + /// Extension property. + /// </summary> + public WktExtension Extension { get; internal set; } + + + /// <summary> + /// Constructor. + /// </summary> + /// <param name="name"></param> + /// <param name="geographicCoordinateSystem"></param> + /// <param name="projection"></param> + /// <param name="parameters"></param> + /// <param name="unit"></param> + /// <param name="axes"></param> + /// <param name="extension"></param> + /// <param name="authority"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="rightDelimiter"></param> + public WktProjectedCoordinateSystem(string name, WktGeographicCoordinateSystem geographicCoordinateSystem, WktProjection projection, + IEnumerable<WktParameter> parameters, WktUnit unit, IEnumerable<WktAxis> axes, + WktExtension extension, WktAuthority authority, + string keyword = "PROJCS", char leftDelimiter = '[', char rightDelimiter = ']') + : base(name, keyword, leftDelimiter, rightDelimiter) + { + GeographicCoordinateSystem = geographicCoordinateSystem; + Projection = projection; + Parameters = parameters ?? new List<WktParameter>(); + Unit = unit; + Axes = axes; + Extension = extension; + Authority = authority; + } + + + /// <summary> + /// Implentation of IEquatable.Equals. + /// </summary> + /// <param name="other"></param> + /// <returns></returns> + public bool Equals(WktProjectedCoordinateSystem other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(GeographicCoordinateSystem, other.GeographicCoordinateSystem) && + Equals(Projection, other.Projection) && + Parameters.SequenceEqual(other.Parameters) && + Equals(Unit, other.Unit) && + Axes.SequenceEqual(other.Axes) && + Equals(Authority, other.Authority) && + Equals(Extension, other.Extension); + } + + /// <summary> + /// Override basic Equals. + /// </summary> + /// <param name="obj"></param> + /// <returns></returns> + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((WktProjectedCoordinateSystem) obj); + } + + /// <summary> + /// Override basic GetHashCode. + /// </summary> + /// <returns></returns> + public override int GetHashCode() + { + unchecked + { + int hashCode = (GeographicCoordinateSystem != null ? GeographicCoordinateSystem.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Projection != null ? Projection.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Parameters != null ? Parameters.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Unit != null ? Unit.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Axes != null ? Axes.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Authority != null ? Authority.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Extension != null ? Extension.GetHashCode() : 0); + return hashCode; + } + } + + + /// <inheritdoc/> + public override void Traverse(IWktTraverseHandler handler) + { + if (GeographicCoordinateSystem!=null) + GeographicCoordinateSystem.Traverse(handler); + if (Projection!=null) + Projection.Traverse(handler); + + if (Parameters != null) + { + foreach (var p in Parameters) + p.Traverse(handler); + } + + if (Unit!=null) + Unit.Traverse(handler); + + if (Axes != null) + { + foreach (var axis in Axes) + axis.Traverse(handler); + } + + if (Authority!=null) + Authority.Traverse(handler); + if (Extension!=null) + Extension.Traverse(handler); + + handler.Handle(this); + } + + + /// <inheritdoc/> + public override string ToString(IWktOutputFormatter formatter) + { + formatter = formatter ?? new DefaultWktOutputFormatter(); + + var result = new StringBuilder(); + + formatter + .AppendKeyword(Keyword, result) + .AppendLeftDelimiter(LeftDelimiter, result) + .AppendQuotedText(Name, result) + + .AppendSeparator(result) + + .AppendExtraWhitespace(result) + .Append(GeographicCoordinateSystem.ToString(formatter), result) + + .AppendSeparator(result) + .AppendExtraWhitespace(result) + .Append(Projection.ToString(formatter), result); + + foreach (var p in Parameters) + { + formatter + .AppendSeparator(result) + .AppendExtraWhitespace(result) + .Append(p.ToString(formatter), result); + } + + formatter + .AppendSeparator(result) + .AppendExtraWhitespace(result) + .Append(Unit.ToString(formatter), result); + + foreach (var ax in Axes) + { + formatter + .AppendSeparator(result) + .AppendExtraWhitespace(result) + .Append(ax.ToString(formatter), result); + } + + if (Extension != null) + { + formatter + .AppendSeparator(result) + .AppendExtraWhitespace(result) + .Append(Extension.ToString(formatter), result); + } + + if (Authority != null) + { + formatter + .AppendSeparator(result) + .AppendIndent(result) + .Append(Authority.ToString(formatter), result); + } + + formatter + .AppendRightDelimiter(RightDelimiter, result); + + return result.ToString(); + } + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktProjection.cs b/src/ProjNet.IO.Wkt/Tree/WktProjection.cs new file mode 100644 index 0000000..f7cccb9 --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktProjection.cs @@ -0,0 +1,115 @@ +using ProjNet.IO.Wkt.Core; +using System; +using System.Text; + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// WktProjection class. + /// </summary> + public class WktProjection : WktBaseObject, IEquatable<WktProjection> + { + /// <summary> + /// Name property. + /// </summary> + public string Name { get; internal set; } + + + /// <summary> + /// Authority property. + /// </summary> + public WktAuthority Authority { get; internal set; } + + + /// <summary> + /// Constructor for WKT Projection element. + /// </summary> + /// <param name="name"></param> + /// <param name="authority"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="rightDelimiter"></param> + public WktProjection(string name, WktAuthority authority, + string keyword = "PROJECTION", char leftDelimiter = '[', char rightDelimiter = ']') + : base(keyword, leftDelimiter, rightDelimiter) + { + Name = name; + Authority = authority; + } + + + /// <summary> + /// IEquatable.Equals implementation checking the whole tree. + /// </summary> + /// <param name="other"></param> + /// <returns></returns> + public bool Equals(WktProjection other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Name == other.Name && Equals(Authority, other.Authority); + } + + /// <summary> + /// Basic override of Equals method. + /// </summary> + /// <param name="obj"></param> + /// <returns></returns> + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((WktProjection) obj); + } + + /// <summary> + /// Override of basic GetHashCode method. + /// </summary> + /// <returns></returns> + public override int GetHashCode() + { + unchecked + { + return ((Name != null ? Name.GetHashCode() : 0) * 397) ^ (Authority != null ? Authority.GetHashCode() : 0); + } + } + + + /// <inheritdoc/> + public override void Traverse(IWktTraverseHandler handler) + { + if (Authority!=null) + Authority.Traverse(handler); + + handler.Handle(this); + } + + + /// <inheritdoc/> + public override string ToString(IWktOutputFormatter formatter) + { + formatter = formatter ?? new DefaultWktOutputFormatter(); + + var result = new StringBuilder(); + + formatter + .AppendKeyword(Keyword, result) + .AppendLeftDelimiter(LeftDelimiter, result) + .AppendQuotedText(Name, result); + + if (Authority != null) + { + formatter + .AppendSeparator(result, keepInside: true) + .AppendExtraWhitespace(result) + .Append(Authority.ToString(formatter), result); + } + + formatter + .AppendRightDelimiter(RightDelimiter, result); + + return result.ToString(); + } + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktRemark.cs b/src/ProjNet.IO.Wkt/Tree/WktRemark.cs new file mode 100644 index 0000000..aeea4be --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktRemark.cs @@ -0,0 +1,49 @@ +using System.Text; + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// Remark a simple wkt Attribute. + /// </summary> + public class WktRemark: IWktAttribute + { + /// <summary> + /// Text property. + /// </summary> + public string Text { get; set; } + + /// <summary> + /// Constructor + /// </summary> + /// <param name="text"></param> + public WktRemark(string text) + { + Text = text; + } + + /// <summary> + /// ToWKT version of this WKT Remark. + /// </summary> + /// <returns></returns> + public string ToWKT() + { + var sb = new StringBuilder(); + + sb.Append($@"REMARK["""); + sb.Append(Text); + sb.Append($@"""]"); + + return sb.ToString(); + } + + /// <summary> + /// ToString version of this WKT Uri. + /// </summary> + /// <returns></returns> + public override string ToString() + { + return Text; + } + + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktScope.cs b/src/ProjNet.IO.Wkt/Tree/WktScope.cs new file mode 100644 index 0000000..2032ae2 --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktScope.cs @@ -0,0 +1,34 @@ +using System.Globalization; +using System.Text; + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// ScopeParser is an optional attribute which describes the purpose or purposes for which a CRS or a coordinate operation is applied. + /// </summary> + /// <remarks> + /// See 7.3.2 ScopeParser in specification document. + /// </remarks> + public class WktScope : IWktAttribute + { + /// <summary> + /// The Text Description. + /// </summary> + public string Description { get; set; } + + /// <summary> + /// ScopeParser to WKT. + /// </summary> + /// <returns></returns> + public string ToWKT() + { + var sb = new StringBuilder(); + + sb.Append("SCOPE[\""); + sb.Append(Description); + sb.Append("\"]"); + + return sb.ToString(); + } + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktScopeExtentIdentifierRemarkElement.cs b/src/ProjNet.IO.Wkt/Tree/WktScopeExtentIdentifierRemarkElement.cs new file mode 100644 index 0000000..b0b094d --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktScopeExtentIdentifierRemarkElement.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using System.Text; + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// WktScopeExtentIdentifierRemarkElement class. + /// </summary> + public class WktScopeExtentIdentifierRemarkElement : IWktAttribute + { + /// <summary> + /// Optional Scope attribute. + /// </summary> + public WktScope Scope { get; set; } + + /// <summary> + /// Zero or more Extent attribute(s). + /// </summary> + public IList<WktExtent> Extents { get; set; } = new List<WktExtent>(); + + /// <summary> + /// Zero or more Identifier attribute(s). + /// </summary> + public IList<WktIdentifier> Identifiers { get; set; } = new List<WktIdentifier>(); + + /// <summary> + /// Optional Remark attrbiute. + /// </summary> + public WktRemark Remark { get; set; } + + + /// <summary> + /// Constructor + /// </summary> + /// <param name="scope"></param> + /// <param name="extents"></param> + /// <param name="identifiers"></param> + /// <param name="remark"></param> + public WktScopeExtentIdentifierRemarkElement( + WktScope scope = null, + IList<WktExtent> extents = null, + IList<WktIdentifier> identifiers = null, + WktRemark remark = null) + { + Scope = scope; + Extents = extents ?? Extents; + Identifiers = identifiers ?? Identifiers; + Remark = remark; + } + + /// <summary> + /// ToWKT. + /// </summary> + /// <returns></returns> + public string ToWKT() + { + var sb = new StringBuilder(); + + if (Scope != null) + { + sb.Append($",{Scope.ToWKT()}"); + } + + if (Extents != null) + { + foreach (var extent in Extents) + { + sb.Append($",{extent.ToWKT()})"); + } + } + if (Identifiers != null) + { + foreach (var identifier in Identifiers) + { + sb.Append($",{identifier.ToWKT()})"); + } + } + + if (Remark != null) + { + sb.Append($",{Remark.ToWKT()}"); + } + + return sb.ToString(); + } + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktSpheroid.cs b/src/ProjNet.IO.Wkt/Tree/WktSpheroid.cs new file mode 100644 index 0000000..4a4f7dd --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktSpheroid.cs @@ -0,0 +1,80 @@ +using System; +using ProjNet.IO.Wkt.Core; + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// WktSpheroid class. + /// </summary> + public class WktSpheroid : WktEllipsoid, IEquatable<WktSpheroid> + { + /// <summary> + /// Constructor for a WktSpheroid. + /// </summary> + /// <param name="name"></param> + /// <param name="semiMajorAxis"></param> + /// <param name="inverseFlattening"></param> + /// <param name="authority"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="rightDelimiter"></param> + public WktSpheroid(string name, double semiMajorAxis, double inverseFlattening, WktAuthority authority, + string keyword = "SPHEROID", char leftDelimiter = '[', char rightDelimiter = ']') + : base(name, semiMajorAxis, inverseFlattening, authority, keyword, leftDelimiter, rightDelimiter) + { + } + + + + /// <summary> + /// IEquatable.Equals implementation checking the whole tree. + /// </summary> + /// <param name="other"></param> + /// <returns></returns> + public bool Equals(WktSpheroid other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Name == other.Name && SemiMajorAxis.Equals(other.SemiMajorAxis) && InverseFlattening.Equals(other.InverseFlattening) && Equals(Authority, other.Authority); + } + + /// <summary> + /// Override of basic Equals. + /// </summary> + /// <param name="obj"></param> + /// <returns></returns> + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((WktSpheroid) obj); + } + + /// <summary> + /// Override of basic GetHashCode. + /// </summary> + /// <returns></returns> + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ (Name != null ? Name.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ SemiMajorAxis.GetHashCode(); + hashCode = (hashCode * 397) ^ InverseFlattening.GetHashCode(); + hashCode = (hashCode * 397) ^ (Authority != null ? Authority.GetHashCode() : 0); + return hashCode; + } + } + + /// <inheritdoc/> + public override void Traverse(IWktTraverseHandler handler) + { + if (Authority!=null) + Authority.Traverse(handler); + + handler.Handle(this); + } + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktTemporalExtent.cs b/src/ProjNet.IO.Wkt/Tree/WktTemporalExtent.cs new file mode 100644 index 0000000..64ae51a --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktTemporalExtent.cs @@ -0,0 +1,74 @@ +using System; +using System.Globalization; +using System.Text; + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// Temporal extent is an optional attribute which describes a date or time + /// range over which a CRS or coordinate operation is applicable. + /// The format for date and time values is defined in ISO 9075-2. Start time is earlier than end time. + /// </summary> + /// <remarks> + /// See 7.3.3.5 Temporal extent in specification document. + /// </remarks> + public class WktTemporalExtent : WktExtent + { + /// <summary> + /// StartDateTime + /// </summary> + public DateTimeOffset? StartDateTime { get; set; } + + /// <summary> + /// StartText + /// </summary> + public string StartText { get; set; } + + + /// <summary> + /// EndDateTime + /// </summary> + public DateTimeOffset? EndDateTime { get; set; } + + /// <summary> + /// EndText + /// </summary> + public string EndText { get; set; } + + + + /// <summary> + /// TemporalExtent to WKT. + /// </summary> + /// <returns></returns> + public override string ToWKT() + { + var sb = new StringBuilder(); + + sb.Append("TIMEEXTENT["); + + if (StartDateTime.HasValue) + sb.Append(StartDateTime.Value.Date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); + else + { + sb.Append(@""""); + sb.Append(StartText); + sb.Append(@""""); + } + + sb.Append(","); + + if (EndDateTime.HasValue) + sb.Append(EndDateTime.Value.Date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); + else + { + sb.Append(@""""); + sb.Append(EndText); + sb.Append(@""""); + } + sb.Append("]"); + + return sb.ToString(); + } + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktToWgs84.cs b/src/ProjNet.IO.Wkt/Tree/WktToWgs84.cs new file mode 100644 index 0000000..b82882a --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktToWgs84.cs @@ -0,0 +1,183 @@ +using System; +using System.Text; +using ProjNet.IO.Wkt.Core; + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// WktToWgs84 - Simple POCO for ToWgs84. + /// </summary> + public class WktToWgs84 : WktBaseObject, IEquatable<WktToWgs84> + { + + /// <summary> + /// DxShift property. + /// </summary> + public double DxShift { get; internal set; } + + /// <summary> + /// DyShift property. + /// </summary> + public double DyShift { get; internal set; } + + /// <summary> + /// DzShift Property. + /// </summary> + public double DzShift { get; internal set; } + + /// <summary> + /// ExRotation property. + /// </summary> + public double ExRotation { get; internal set; } + /// <summary> + /// EyRotation property. + /// </summary> + public double EyRotation { get; internal set; } + /// <summary> + /// EzRotation property. + /// </summary> + public double EzRotation { get; internal set; } + /// <summary> + /// PpmScaling property. + /// </summary> + public double PpmScaling { get; internal set; } + + /// <summary> + /// Description property. + /// </summary> + public string Description { get; internal set; } + + + /// <summary> + /// Constructor for WktToWgs84 class. + /// </summary> + /// <param name="dxShift"></param> + /// <param name="dyShift"></param> + /// <param name="dzShift"></param> + /// <param name="exRotation"></param> + /// <param name="eyRotation"></param> + /// <param name="ezRotation"></param> + /// <param name="ppmScaling"></param> + /// <param name="description"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="rightDelimiter"></param> + public WktToWgs84(double dxShift, double dyShift, double dzShift, + double exRotation, double eyRotation, double ezRotation, + double ppmScaling, string description = default, + string keyword = "TOWGS84", char leftDelimiter = '[', char rightDelimiter = ']') + : base(keyword, leftDelimiter, rightDelimiter) + { + DxShift = dxShift; + DyShift = dyShift; + DzShift = dzShift; + + ExRotation = exRotation; + EyRotation = eyRotation; + EzRotation = ezRotation; + + PpmScaling = ppmScaling; + + Description = description; + } + + + /// <summary> + /// IEquatable.Equals implementation checking the whole tree. + /// </summary> + /// <param name="other"></param> + /// <returns></returns> + public bool Equals(WktToWgs84 other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return DxShift.Equals(other.DxShift) && DyShift.Equals(other.DyShift) && DzShift.Equals(other.DzShift) && ExRotation.Equals(other.ExRotation) && EyRotation.Equals(other.EyRotation) && EzRotation.Equals(other.EzRotation) && PpmScaling.Equals(other.PpmScaling) && Description == other.Description; + } + + /// <summary> + /// Basic override of Equals. + /// </summary> + /// <param name="obj"></param> + /// <returns></returns> + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((WktToWgs84) obj); + } + + /// <summary> + /// Basic override of GetHashCode. + /// </summary> + /// <returns></returns> + public override int GetHashCode() + { + unchecked + { + int hashCode = DxShift.GetHashCode(); + hashCode = (hashCode * 397) ^ DyShift.GetHashCode(); + hashCode = (hashCode * 397) ^ DzShift.GetHashCode(); + hashCode = (hashCode * 397) ^ ExRotation.GetHashCode(); + hashCode = (hashCode * 397) ^ EyRotation.GetHashCode(); + hashCode = (hashCode * 397) ^ EzRotation.GetHashCode(); + hashCode = (hashCode * 397) ^ PpmScaling.GetHashCode(); + hashCode = (hashCode * 397) ^ (Description != null ? Description.GetHashCode() : 0); + return hashCode; + } + } + + /// <inheritdoc/> + public override void Traverse(IWktTraverseHandler handler) + { + handler.Handle(this); + } + + + /// <inheritdoc/> + public override string ToString(IWktOutputFormatter formatter) + { + formatter = formatter ?? new DefaultWktOutputFormatter(); + + var result = new StringBuilder(); + + formatter + .AppendKeyword(Keyword, result) + .AppendLeftDelimiter(LeftDelimiter, result) + .Append(DxShift, result) + .AppendSeparator(result, keepInside: true) + .AppendExtraWhitespace(result) + .Append(DyShift, result) + .AppendSeparator(result, keepInside: true) + .AppendExtraWhitespace(result) + .Append(DzShift, result) + .AppendSeparator(result, keepInside: true) + .AppendExtraWhitespace(result) + + .Append(ExRotation, result) + .AppendSeparator(result, keepInside: true) + .AppendExtraWhitespace(result) + .Append(EyRotation, result) + .AppendSeparator(result, keepInside: true) + .AppendExtraWhitespace(result) + .Append(EzRotation, result) + .AppendSeparator(result, keepInside: true) + .AppendExtraWhitespace(result) + + .Append(PpmScaling, result); + + if (!string.IsNullOrWhiteSpace(Description)) + { + formatter + .AppendSeparator(result, keepInside: true) + .AppendExtraWhitespace(result) + .AppendQuotedText(Description, result); + } + + formatter + .AppendRightDelimiter(RightDelimiter, result); + + return result.ToString(); + } + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktUnit.cs b/src/ProjNet.IO.Wkt/Tree/WktUnit.cs new file mode 100644 index 0000000..8538f5d --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktUnit.cs @@ -0,0 +1,127 @@ +using System; +using System.Text; +using ProjNet.IO.Wkt.Core; + + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// WktUnit class. + /// </summary> + public class WktUnit : WktBaseObject, IEquatable<WktUnit> + { + /// <summary> + /// Name property. + /// </summary> + public string Name { get; internal set; } + + /// <summary> + /// ConversionFactor property. + /// </summary> + public double ConversionFactor { get; internal set; } + + /// <summary> + /// Authority property. (Optional) + /// </summary> + public WktAuthority Authority { get; internal set; } + + + /// <summary> + /// Constructor for WKT Unit. + /// </summary> + /// <param name="name"></param> + /// <param name="conversionFactor"></param> + /// <param name="authority"></param> + /// <param name="keyword"></param> + /// <param name="leftDelimiter"></param> + /// <param name="rightDelimiter"></param> + public WktUnit(string name, double conversionFactor, WktAuthority authority, + string keyword = "UNIT", char leftDelimiter = '[', char rightDelimiter = ']') + : base(keyword, leftDelimiter, rightDelimiter) + { + Name = name; + ConversionFactor = conversionFactor; + Authority = authority; + } + + + /// <summary> + /// Implementing IEquatable.Equals checking the whole tree. + /// </summary> + /// <param name="other"></param> + /// <returns></returns> + public bool Equals(WktUnit other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Name == other.Name && ConversionFactor.Equals(other.ConversionFactor) && Equals(Authority, other.Authority); + } + + /// <summary> + /// Overriding basic Equals. + /// </summary> + /// <param name="obj"></param> + /// <returns></returns> + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((WktUnit) obj); + } + + /// <summary> + /// Overriding basic GetHashCode method. + /// </summary> + /// <returns></returns> + public override int GetHashCode() + { + unchecked + { + int hashCode = (Name != null ? Name.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ ConversionFactor.GetHashCode(); + hashCode = (hashCode * 397) ^ (Authority != null ? Authority.GetHashCode() : 0); + return hashCode; + } + } + + + /// <inheritdoc/> + public override void Traverse(IWktTraverseHandler handler) + { + if (Authority != null) + Authority.Traverse(handler); + + handler.Handle(this); + } + + /// <inheritdoc/> + public override string ToString(IWktOutputFormatter formatter) + { + formatter = formatter ?? new DefaultWktOutputFormatter(); + + var result = new StringBuilder(); + + formatter + .AppendKeyword(Keyword, result) + .AppendLeftDelimiter(LeftDelimiter, result) + .AppendQuotedText(Name, result) + .AppendSeparator(result, keepInside: true) + .AppendExtraWhitespace(result) + .Append(ConversionFactor, result); + + if (Authority != null) + { + formatter + .AppendSeparator(result, keepInside: true) + .AppendExtraWhitespace(result) + .Append(Authority.ToString(formatter), result); + } + + formatter + .AppendRightDelimiter(RightDelimiter, result); + + return result.ToString(); + } + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktUri.cs b/src/ProjNet.IO.Wkt/Tree/WktUri.cs new file mode 100644 index 0000000..f924531 --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktUri.cs @@ -0,0 +1,49 @@ +using System.Text; + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// Uri a simple wkt helper class containing a Uri (sub) Attribute. + /// </summary> + public class WktUri: IWktAttribute + { + /// <summary> + /// Ref property. + /// </summary> + public string Ref { get; set; } + + /// <summary> + /// Constructor. + /// </summary> + /// <param name="uriRef"></param> + public WktUri(string uriRef) + { + Ref = uriRef; + } + + /// <summary> + /// ToWKT version of this WKT Uri. + /// </summary> + /// <returns></returns> + public string ToWKT() + { + var sb = new StringBuilder(); + + sb.Append($@"URI["""); + sb.Append(Ref); + sb.Append($@"""]"); + + return sb.ToString(); + } + + /// <summary> + /// ToString version of this WKT Uri. + /// </summary> + /// <returns></returns> + public override string ToString() + { + return Ref; + } + + } +} diff --git a/src/ProjNet.IO.Wkt/Tree/WktVerticalExtent.cs b/src/ProjNet.IO.Wkt/Tree/WktVerticalExtent.cs new file mode 100644 index 0000000..17e0ace --- /dev/null +++ b/src/ProjNet.IO.Wkt/Tree/WktVerticalExtent.cs @@ -0,0 +1,50 @@ +using System.Globalization; +using System.Text; + +namespace ProjNet.IO.Wkt.Tree +{ + /// <summary> + /// Vertical extent is an optional attribute which describes a height range over + /// which a CRS or coordinate operation is applicable. Depths have negative height values. + /// Vertical extent is an approximate description of location; heights are relative to an unspecified mean sea level. + /// </summary> + /// <remarks> + /// See 7.3.3.4 Vertical extent in specification document. + /// </remarks> + public class WktVerticalExtent : WktExtent + { + /// <summary> + /// MinimumHeight + /// </summary> + public double MinimumHeight { get; set; } + + /// <summary> + /// MaximumHeight + /// </summary> + public double MaximumHeight { get; set; } + + + //// <summary> + //// Optional LengthUnit. + //// </summary> + // TODO: public LengthUnit? LengthUnit { get; set; } + + + /// <summary> + /// VerticalExtent to WKT. + /// </summary> + /// <returns></returns> + public override string ToWKT() + { + var sb = new StringBuilder(); + + sb.Append("VERTICALEXTENT["); + sb.Append(MinimumHeight.ToString(CultureInfo.InvariantCulture)); + sb.Append(","); + sb.Append(MaximumHeight.ToString(CultureInfo.InvariantCulture)); + sb.Append("]"); + + return sb.ToString(); + } + } +} diff --git a/src/ProjNet.IO.Wkt/Utils/DateTimeBuilder.cs b/src/ProjNet.IO.Wkt/Utils/DateTimeBuilder.cs new file mode 100644 index 0000000..48b8cb7 --- /dev/null +++ b/src/ProjNet.IO.Wkt/Utils/DateTimeBuilder.cs @@ -0,0 +1,119 @@ +using System; +using System.Globalization; +using Pidgin; + +namespace ProjNet.IO.Wkt.Utils +{ + internal sealed class DateTimeBuilder + { + internal int? Year { get; set; } + internal int? Month { get; set; } + internal int? Day { get; set; } + + internal uint? OrdinalDay { get; set; } + + internal int? Hour { get; set; } + internal int? Minutes { get; set; } + internal int? Seconds { get; set; } + internal int? Milliseconds { get; set; } + + internal TimeSpan? LocalOffset { get; set; } + + internal DateTimeKind Kind { get; set; } = DateTimeKind.Unspecified; + + + public DateTimeBuilder SetYear(int y) + { + Year = y; + return this; + } + public DateTimeBuilder SetMonth(int m) + { + Month = m; + return this; + } + public DateTimeBuilder SetDay(int d) + { + Day = d; + return this; + } + + public DateTimeBuilder SetHour(int h) + { + Hour = h; + return this; + } + public DateTimeBuilder SetMinutes(int m) + { + Minutes = m; + return this; + } + public DateTimeBuilder SetSeconds(int s) + { + Seconds = s; + return this; + } + public DateTimeBuilder SetMilliseconds(int ms) + { + Milliseconds = ms; + return this; + } + + public DateTimeBuilder SetOrdinalDay(Maybe<uint> od) + { + OrdinalDay = od.HasValue ? od.GetValueOrDefault() : OrdinalDay; + return this; + } + + + public DateTimeBuilder SetKind(DateTimeKind k) + { + Kind = k; + return this; + } + + public DateTimeBuilder SetLocalOffset(TimeSpan lo) + { + if (lo == TimeSpan.Zero) + { + Kind = DateTimeKind.Utc; + } + else + { + LocalOffset = lo; + Kind = DateTimeKind.Local; + } + + return this; + } + + public DateTimeBuilder Merge(DateTimeBuilder other) + { + Day = Day ?? other.Day; + Month = Month ?? other.Month; + Year = Year ?? other.Year; + + Hour = Hour ?? other.Hour; + Minutes = Minutes ?? other.Minutes; + Seconds = Seconds ?? other.Seconds; + Milliseconds = Milliseconds ?? other.Milliseconds; + + OrdinalDay = OrdinalDay ?? other.OrdinalDay; + Kind = Kind == DateTimeKind.Unspecified ? other.Kind : Kind; + LocalOffset = LocalOffset ?? other.LocalOffset; + + return this; + } + + public DateTimeOffset ToDateTimeOffset() + { + var dt = new DateTime(Year.GetValueOrDefault(), Month.GetValueOrDefault(), Day.GetValueOrDefault(), + Hour.GetValueOrDefault(), Minutes.GetValueOrDefault(), Seconds.GetValueOrDefault(), + Milliseconds.GetValueOrDefault(), + new GregorianCalendar(), + Kind); + + return new DateTimeOffset(dt, LocalOffset.GetValueOrDefault()); + } + } +} diff --git a/src/ProjNet.IO.Wkt/Utils/Utils.cs b/src/ProjNet.IO.Wkt/Utils/Utils.cs new file mode 100644 index 0000000..97fab2f --- /dev/null +++ b/src/ProjNet.IO.Wkt/Utils/Utils.cs @@ -0,0 +1,26 @@ +using System; +using System.Globalization; + +namespace ProjNet.IO.Wkt.Utils +{ + /// <summary> + /// Helper functions for WKT Parser(s). + /// </summary> + public class Utils + { + internal static double CalcAsFractionOf(uint i, string fraction) + { + int fractionDigits = fraction.Length; + + double d = i; + double f = double.Parse(fraction, NumberStyles.Any, CultureInfo.InvariantCulture); + + // Calculate the fractional part from f based on the number of fractional digits + double divisor = Math.Pow(10, fractionDigits); + double fractionPart = f / divisor; + + // Sum i and the fractional part + return d + fractionPart; + } + } +} diff --git a/src/ProjNet/CoordinateSystems/CoordinateSystemFactory.cs b/src/ProjNet/CoordinateSystems/CoordinateSystemFactory.cs index 28d831d..f4f575f 100644 --- a/src/ProjNet/CoordinateSystems/CoordinateSystemFactory.cs +++ b/src/ProjNet/CoordinateSystems/CoordinateSystemFactory.cs @@ -5,7 +5,7 @@ // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. -// +// // ProjNet is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the @@ -13,12 +13,15 @@ // You should have received a copy of the GNU Lesser General Public License // along with ProjNet; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA using System; using System.Collections.Generic; +using System.IO; using System.Text; +using Pidgin; using ProjNet.IO.CoordinateSystems; +using ProjNet.Wkt; namespace ProjNet.CoordinateSystems { @@ -26,15 +29,15 @@ namespace ProjNet.CoordinateSystems /// Builds up complex objects from simpler objects or values. /// </summary> /// <remarks> - /// <para>CoordinateSystemFactory allows applications to make coordinate systems that + /// <para>CoordinateSystemFactory allows applications to make coordinate systems that /// is very flexible, whereas the other factories are easier to use.</para> /// <para>So this Factory can be used to make 'special' coordinate systems.</para> - /// <para>For example, the EPSG authority has codes for USA state plane coordinate systems - /// using the NAD83 datum, but these coordinate systems always use meters. EPSG does not + /// <para>For example, the EPSG authority has codes for USA state plane coordinate systems + /// using the NAD83 datum, but these coordinate systems always use meters. EPSG does not /// have codes for NAD83 state plane coordinate systems that use feet units. This factory /// lets an application create such a hybrid coordinate system.</para> /// </remarks> - public class CoordinateSystemFactory + public class CoordinateSystemFactory { /// <summary> /// Creates an instance of this class @@ -64,7 +67,6 @@ public CoordinateSystem CreateFromWkt(string WKT) return info as CoordinateSystem; } - /// <summary> /// Creates a <see cref="CompoundCoordinateSystem"/> [NOT IMPLEMENTED]. /// </summary> @@ -83,11 +85,11 @@ public CompoundCoordinateSystem CreateCompoundCoordinateSystem(string name, Coor /// <summary> /// Creates a <see cref="FittedCoordinateSystem"/>. /// </summary> - /// <remarks>The units of the axes in the fitted coordinate system will be + /// <remarks>The units of the axes in the fitted coordinate system will be /// inferred from the units of the base coordinate system. If the affine map /// performs a rotation, then any mixed axes must have identical units. For - /// example, a (lat_deg,lon_deg,height_feet) system can be rotated in the - /// (lat,lon) plane, since both affected axes are in degrees. But you + /// example, a (lat_deg,lon_deg,height_feet) system can be rotated in the + /// (lat,lon) plane, since both affected axes are in degrees. But you /// should not rotate this coordinate system in any other plane.</remarks> /// <param name="name">Name of coordinate system</param> /// <param name="baseCoordinateSystem">Base coordinate system</param> @@ -122,9 +124,9 @@ public FittedCoordinateSystem CreateFittedCoordinateSystem(string name, Coordina /// Creates a <see cref="ILocalCoordinateSystem">local coordinate system</see>. /// </summary> /// <remarks> - /// The dimension of the local coordinate system is determined by the size of - /// the axis array. All the axes will have the same units. If you want to make - /// a coordinate system with mixed units, then you can make a compound + /// The dimension of the local coordinate system is determined by the size of + /// the axis array. All the axes will have the same units. If you want to make + /// a coordinate system with mixed units, then you can make a compound /// coordinate system from different local coordinate systems. /// </remarks> /// <param name="name">Name of local coordinate system</param> @@ -219,9 +221,9 @@ public IProjection CreateProjection(string name, string wktProjectionClass, List /// Creates <see cref="HorizontalDatum"/> from ellipsoid and Bursa-World parameters. /// </summary> /// <remarks> - /// Since this method contains a set of Bursa-Wolf parameters, the created + /// Since this method contains a set of Bursa-Wolf parameters, the created /// datum will always have a relationship to WGS84. If you wish to create a - /// horizontal datum that has no relationship with WGS84, then you can + /// horizontal datum that has no relationship with WGS84, then you can /// either specify a <see cref="DatumType">horizontalDatumType</see> of <see cref="DatumType.HD_Other"/>, or create it via WKT. /// </remarks> /// <param name="name">Name of ellipsoid</param> @@ -294,7 +296,7 @@ public ILocalDatum CreateLocalDatum(string name, DatumType datumType) /// </summary> /// <param name="name">Name of datum</param> /// <param name="datumType">Type of datum</param> - /// <returns>Vertical datum</returns> + /// <returns>Vertical datum</returns> public VerticalDatum CreateVerticalDatum(string name, DatumType datumType) { if (string.IsNullOrWhiteSpace(name)) @@ -322,7 +324,7 @@ public VerticalCoordinateSystem CreateVerticalCoordinateSystem(string name, Vert } /// <summary> - /// Creates a <see cref="CreateGeocentricCoordinateSystem"/> from a <see cref="HorizontalDatum">datum</see>, + /// Creates a <see cref="CreateGeocentricCoordinateSystem"/> from a <see cref="HorizontalDatum">datum</see>, /// <see cref="LinearUnit">linear unit</see> and <see cref="PrimeMeridian"/>. /// </summary> /// <param name="name">Name of geocentric coordinate system</param> diff --git a/src/ProjNet/CoordinateSystems/Transformations/CoordinateTransformation.cs b/src/ProjNet/CoordinateSystems/Transformations/CoordinateTransformation.cs index b44570c..c26e460 100644 --- a/src/ProjNet/CoordinateSystems/Transformations/CoordinateTransformation.cs +++ b/src/ProjNet/CoordinateSystems/Transformations/CoordinateTransformation.cs @@ -5,7 +5,7 @@ // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. -// +// // ProjNet is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the @@ -13,18 +13,18 @@ // You should have received a copy of the GNU Lesser General Public License // along with ProjNet; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA using System; namespace ProjNet.CoordinateSystems.Transformations { /// <summary> - /// Describes a coordinate transformation. This class only describes a - /// coordinate transformation, it does not actually perform the transform + /// Describes a coordinate transformation. This class only describes a + /// coordinate transformation, it does not actually perform the transform /// operation on points. To transform points you must use a <see cref="MathTransform"/>. /// </summary> - [Serializable] + [Serializable] public class CoordinateTransformation : ICoordinateTransformation { /// <summary> @@ -39,7 +39,7 @@ public class CoordinateTransformation : ICoordinateTransformation /// <param name="authorityCode">Authority code</param> /// <param name="areaOfUse">Area of use</param> /// <param name="remarks">Remarks</param> - internal CoordinateTransformation(CoordinateSystem sourceCS, CoordinateSystem targetCS, TransformType transformType, MathTransform mathTransform, + internal CoordinateTransformation(CoordinateSystem sourceCS, CoordinateSystem targetCS, TransformType transformType, MathTransform mathTransform, string name, string authority, long authorityCode, string areaOfUse, string remarks) { TargetCS = targetCS; @@ -50,7 +50,7 @@ internal CoordinateTransformation(CoordinateSystem sourceCS, CoordinateSystem ta Authority = authority; AuthorityCode = authorityCode; AreaOfUse = areaOfUse; - Remarks = remarks; + Remarks = remarks; } @@ -59,21 +59,21 @@ internal CoordinateTransformation(CoordinateSystem sourceCS, CoordinateSystem ta /// <summary> /// Human readable description of domain in source coordinate system. - /// </summary> + /// </summary> public string AreaOfUse { get; } /// <summary> /// Authority which defined transformation and parameter values. /// </summary> /// <remarks> - /// An Authority is an organization that maintains definitions of Authority Codes. For example the European Petroleum Survey Group (EPSG) maintains a database of coordinate systems, and other spatial referencing objects, where each object has a code number ID. For example, the EPSG code for a WGS84 Lat/Lon coordinate system is �4326� + /// An Authority is an organization that maintains definitions of Authority Codes. For example the European Petroleum Survey Group (EPSG) maintains a database of coordinate systems, and other spatial referencing objects, where each object has a code number ID. For example, the EPSG code for a WGS84 Lat/Lon coordinate system is �4326� /// </remarks> public string Authority { get; } /// <summary> - /// Code used by authority to identify transformation. An empty string is used for no code. + /// Direction used by authority to identify transformation. An empty string is used for no code. /// </summary> - /// <remarks>The AuthorityCode is a compact string defined by an Authority to reference a particular spatial reference object. For example, the European Survey Group (EPSG) authority uses 32 bit integers to reference coordinate systems, so all their code strings will consist of a few digits. The EPSG code for WGS84 Lat/Lon is �4326�.</remarks> + /// <remarks>The AuthorityCode is a compact string defined by an Authority to reference a particular spatial reference object. For example, the European Survey Group (EPSG) authority uses 32 bit integers to reference coordinate systems, so all their code strings will consist of a few digits. The EPSG code for WGS84 Lat/Lon is �4326�.</remarks> public long AuthorityCode { get; } /// <summary> diff --git a/src/ProjNet/CoordinateSystems/Transformations/ICoordinateTransformation.cs b/src/ProjNet/CoordinateSystems/Transformations/ICoordinateTransformation.cs index 7c48311..3737550 100644 --- a/src/ProjNet/CoordinateSystems/Transformations/ICoordinateTransformation.cs +++ b/src/ProjNet/CoordinateSystems/Transformations/ICoordinateTransformation.cs @@ -55,7 +55,7 @@ public interface ICoordinateTransformation : ICoordinateTransformationCore string Authority { get; } /// <summary> - /// Code used by authority to identify transformation. An empty string is used for no code. + /// Direction used by authority to identify transformation. An empty string is used for no code. /// </summary> /// <remarks>The AuthorityCode is a compact string defined by an Authority to reference a particular spatial reference object. For example, the European Survey Group (EPSG) authority uses 32 bit integers to reference coordinate systems, so all their code strings will consist of a few digits. The EPSG code for WGS84 Lat/Lon is �4326�.</remarks> long AuthorityCode { get; } diff --git a/src/ProjNet/IO/CoordinateSystems/StreamTokenizer.cs b/src/ProjNet/IO/CoordinateSystems/StreamTokenizer.cs index 0ebf5df..e2a6395 100644 --- a/src/ProjNet/IO/CoordinateSystems/StreamTokenizer.cs +++ b/src/ProjNet/IO/CoordinateSystems/StreamTokenizer.cs @@ -5,7 +5,7 @@ // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. -// +// // ProjNet is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the @@ -13,11 +13,11 @@ // You should have received a copy of the GNU Lesser General Public License // along with ProjNet; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // SOURCECODE IS MODIFIED FROM ANOTHER WORK AND IS ORIGINALLY BASED ON GeoTools.NET: /* - * Copyright (C) 2002 Urban Science Applications, Inc. + * Copyright (C) 2002 Urban Science Applications, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -45,7 +45,7 @@ namespace ProjNet.IO.CoordinateSystems ///The StreamTokenizer class takes an input stream and parses it into "tokens", allowing the tokens to be read one at a time. The parsing process is controlled by a table and a number of flags that can be set to various states. The stream tokenizer can recognize identifiers, numbers, quoted strings, and various comment style ///</summary> ///<remarks> - ///This is a crude c# implementation of Java's <a href="http://java.sun.com/products/jdk/1.2/docs/api/java/io/StreamTokenizer.html">StreamTokenizer</a> class. + ///This is a crude c# implementation of Java's <a href="http://java.sun.com/products/jdk/1.2/Docs/api/java/io/StreamTokenizer.html">StreamTokenizer</a> class. ///</remarks> internal class StreamTokenizer { @@ -57,13 +57,13 @@ internal class StreamTokenizer private int _lineNumber = 1; private int _colNumber = 1; - private readonly bool _ignoreWhitespace; + private readonly bool _ignoreWhitespace; /// <summary> /// Initializes a new instance of the StreamTokenizer class. /// </summary> /// <param name="reader">A TextReader with some text to read.</param> - /// <param name="ignoreWhitespace">Flag indicating whether whitespace should be ignored.</param> + /// <param name="ignoreWhitespace">Flag indicating whether whitespace should be ignored.</param> public StreamTokenizer(TextReader reader, bool ignoreWhitespace) { if (reader == null) @@ -95,7 +95,7 @@ public bool IgnoreWhitespace } /// <summary> - /// If the current token is a number, this field contains the value of that number. + /// If the current token is a number, this field contains the value of that number. /// </summary> /// <remarks> /// If the current token is a number, this field contains the value of that number. The current token is a number when the value of the ttype field is TT_NUMBER. @@ -112,7 +112,7 @@ public double GetNumericValue() } /// <summary> - /// If the current token is a word token, this field contains a string giving the characters of the word token. + /// If the current token is a word token, this field contains a string giving the characters of the word token. /// </summary> public string GetStringValue() { diff --git a/src/ProjNet/ProjNET.csproj b/src/ProjNet/ProjNET.csproj index e5cfcda..e6665f0 100644 --- a/src/ProjNet/ProjNET.csproj +++ b/src/ProjNet/ProjNET.csproj @@ -36,4 +36,8 @@ Proj.NET performs point-to-point coordinate conversions between geodetic coordin <ResolvedMatchingContract Include="$(NugetPackageRoot)projnet\2.0.0\lib\netstandard2.0\ProjNET.dll" /> </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\ProjNet.IO.Wkt\ProjNet.IO.Wkt.csproj" /> + </ItemGroup> + </Project> diff --git a/src/ProjNet/Wkt/WktToProjBuilder.cs b/src/ProjNet/Wkt/WktToProjBuilder.cs new file mode 100644 index 0000000..3e124ad --- /dev/null +++ b/src/ProjNet/Wkt/WktToProjBuilder.cs @@ -0,0 +1,453 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using Pidgin; +using ProjNet.CoordinateSystems; +using ProjNet.CoordinateSystems.Transformations; +using ProjNet.IO.Wkt.Core; + +using static ProjNet.IO.Wkt.Core.WktParser; + + +namespace ProjNet.Wkt +{ + + public class Authority + { + public string Name { get; set; } + + public int Code { get; set; } + } + + + public class WktToProjBuilder : IWktBuilder + { + private readonly CoordinateSystemFactory factory; + private readonly Func<string, Result<char, CoordinateSystem>> Parser; + + + public WktToProjBuilder() + { + factory = new CoordinateSystemFactory(); + + Parser = WktParserResolver<CoordinateSystem>(this); + } + + public object BuildAuthority(int start, string keyword, char leftDelimiter, string name, int code, + char rightDelimiter) + { + return new Authority{Name = name, Code =code}; + } + + public object BuildAxis(int offset, string keyword, char leftDelimiter, string name, string direction, + char rightDelimiter) + { + var orientationEnum = (AxisOrientationEnum)Enum.Parse(typeof(AxisOrientationEnum), direction, true); + return new AxisInfo(name, orientationEnum); + } + + public object BuildExtension(int offset, string keyword, char leftDelimiter, string name, string direction, + char rightDelimiter) + { + return name; + } + + public object BuildToWgs84(int offset, string keyword, char leftDelimiter, double dxShift, double dyShift, double dzShift, + double exRotation, double eyRotation, double ezRotation, double ppmScaling, string description, char rightDelimiter) + { + return new Wgs84ConversionInfo(dxShift, dyShift, dzShift, exRotation, eyRotation, ezRotation, ppmScaling, description); + } + + public object BuildProjection(int offset, string keyword, char leftDelimiter, string name, object authority, + char rightDelimiter) + { + var result = new Projection(name, null, name, string.Empty, -1, string.Empty, + string.Empty, string.Empty); + + if (authority is Authority authObj) + { + result.AuthorityCode = authObj.Code; + result.Authority = authObj.Name; + } + + + return result; + } + + public object BuildProjectionParameter(int offset, string keyword, char leftDelimiter, string name, double value, + char rightDelimiter) + { + return new ProjectionParameter(name, value); + } + + public object BuildParameter(int offset, string keyword, char leftDelimiter, string name, double value, + char rightDelimiter) + { + return new Parameter(name, value); + } + + public object BuildEllipsoid(int offset, string keyword, char leftDelimiter, string name, double semiMajorAxis, + double inverseFlattening, object authority, char rightDelimiter) + { + + var result = new Ellipsoid(semiMajorAxis, 0.0, inverseFlattening, true, LinearUnit.Metre, name, + string.Empty, -1, string.Empty, string.Empty, string.Empty); + + if (authority is Authority authObj) + { + result.AuthorityCode = authObj.Code; + result.Authority = authObj.Name; + } + + return result; + } + + public object BuildSpheroid(int offset, string keyword, char leftDelimiter, string name, double semiMajorAxis, + double inverseFlattening, object authority, char rightDelimiter) + { + + var result = new Ellipsoid(semiMajorAxis, 0.0, inverseFlattening, true, LinearUnit.Metre, name, + string.Empty, -1, string.Empty, string.Empty, string.Empty); + + if (authority is Authority authObj) + { + result.AuthorityCode = authObj.Code; + result.Authority = authObj.Name; + } + + + return result; + } + + public object BuildPrimeMeridian(int offset, string keyword, char leftDelimiter, string name, double longitude, + object authority, char rightDelimiter) + { + var au = (AngularUnit) AngularUnit.Degrees; + + var result = this.factory.CreatePrimeMeridian(name, angularUnit: au, longitude: longitude); + + if (authority is Authority authObj) + { + result.AuthorityCode = authObj.Code; + result.Authority = authObj.Name; + } + + + return result; + } + + public object BuildUnit(int offset, string keyword, char leftDelimiter, string name, double factor, object authority, + char rightDelimiter) + { + + var result = new ProjNet.CoordinateSystems.Unit(factor,name, string.Empty, -1 , string.Empty, string.Empty, string.Empty); + + if (authority is Authority authObj) + { + result.AuthorityCode = authObj.Code; + result.Authority = authObj.Name; + } + + + return result; + } + + public object BuildLinearUnit(int offset, string keyword, char leftDelimiter, string name, double factor, + object authority, char rightDelimiter) + { + var result = new ProjNet.CoordinateSystems.Unit(factor,name, string.Empty, -1, string.Empty, string.Empty, string.Empty); + + if (authority is Authority authObj) + { + result.AuthorityCode = authObj.Code; + result.Authority = authObj.Name; + } + + + return result; + } + + public object BuildAngularUnit(int offset, string keyword, char leftDelimiter, string name, double factor, + object authority, char rightDelimiter) + { + + + var result = new ProjNet.CoordinateSystems.Unit(factor,name, string.Empty, -1, string.Empty, string.Empty, string.Empty); + if (authority is Authority authObj) + { + result.AuthorityCode = authObj.Code; + result.Authority = authObj.Name; + } + + + return result; + } + + public object BuildDatum(int offset, string keyword, char leftDelimiter, string name, object spheroid, object toWgs84, + object authority, char rightDelimiter) + { + var result = this.factory.CreateHorizontalDatum(name, DatumType.HD_Geocentric, ellipsoid:(Ellipsoid)spheroid, toWgs84: (Wgs84ConversionInfo)toWgs84); + + if (authority is Authority authObj) + { + result.AuthorityCode = authObj.Code; + result.Authority = authObj.Name; + } + + + return result; + } + + public object BuildGeographicCoordinateSystem(int offset, string keyword, char leftDelimiter, string name, object datum, + object meridian, object unit, IEnumerable<object> axes, object authority, char rightDelimiter) + { + + var u = (ProjNet.CoordinateSystems.Unit) unit; + AngularUnit au = null; + if (u != null) + { + au = new AngularUnit(u.ConversionFactor, u.Name, u.Authority, u.AuthorityCode, string.Empty, + string.Empty, string.Empty); + } + + IList<AxisInfo> a = axes.Cast<AxisInfo>().ToList(); + //This is default axis values if not specified. + var ax1 = a.ElementAtOrDefault(0); + ax1 = ax1 ?? new AxisInfo("Lon", AxisOrientationEnum.East); + var ax2 = a.ElementAtOrDefault(1); + ax2 = ax2 ?? new AxisInfo("Lat", AxisOrientationEnum.North); + + + var result = this.factory.CreateGeographicCoordinateSystem(name, au, (HorizontalDatum)datum, + (PrimeMeridian)meridian, axis0: ax1, axis1:ax2); + + if (authority is Authority authObj) + { + result.AuthorityCode = authObj.Code; + result.Authority = authObj.Name; + } + + + return result; + } + + public object BuildGeocentricCoordinateSystem(int offset, string keyword, char leftDelimiter, string name, object datum, + object meridian, object unit, IEnumerable<object> axes, object authority, char rightDelimiter) + { + var u = (ProjNet.CoordinateSystems.Unit) unit; + var lu = (LinearUnit) null; + if (u != null) + { + lu = new LinearUnit(u.ConversionFactor, u.Name, u.Authority, u.AuthorityCode, string.Empty, + string.Empty, string.Empty); + } + + var result = + this.factory.CreateGeocentricCoordinateSystem(name, (HorizontalDatum)datum, lu, (PrimeMeridian)meridian); + + if (authority is Authority authObj) + { + result.AuthorityCode = authObj.Code; + result.Authority = authObj.Name; + } + + + return result; + } + + public object BuildProjectedCoordinateSystem(int offset, string keyword, char leftDelimiter, string name, object geogcs, + object projection, object parameters, object unit, IEnumerable<object> axes, object extension, object authority, char rightDelimiter) + { + var gcs = (GeographicCoordinateSystem) geogcs; + var u = (ProjNet.CoordinateSystems.Unit) unit; + var lu = (LinearUnit) null; + if (u != null) + { + lu = new LinearUnit(u.ConversionFactor, u.Name, u.Authority, u.AuthorityCode, string.Empty, + string.Empty, string.Empty); + } + + + var pp = ((List<object>) parameters).Cast<ProjectionParameter>().ToList(); + var p = (Projection) projection; + p.Parameters = new List<ProjectionParameter>(); + p.Parameters.AddRange(pp); + + var a = axes.Cast<AxisInfo>().ToList(); + var ax1 = (AxisInfo)a.ElementAtOrDefault(0); + ax1 = ax1 ?? new AxisInfo("X", AxisOrientationEnum.East); + + var ax2 = (AxisInfo) axes.ElementAtOrDefault(1); + ax2 = ax2 ?? new AxisInfo("Y", AxisOrientationEnum.North); + + var aa = new List<AxisInfo> {ax1, ax2}; + + var result = new ProjectedCoordinateSystem(gcs.HorizontalDatum, gcs, lu, p, aa, name, string.Empty, -1, string.Empty, string.Empty, string.Empty); + + if (authority is Authority authObj) + { + result.AuthorityCode = authObj.Code; + result.Authority = authObj.Name; + } + + + return result; + } + + + + + public object BuildParameterMathTransform(int offset, string keyword, char leftDelimiter, string name, object parameters, + char rightDelimiter) + { + if (name.Equals("Affine", StringComparison.InvariantCultureIgnoreCase)) + { + /* + PARAM_MT[ + "Affine", + PARAMETER["num_row",3], + PARAMETER["num_col",3], + PARAMETER["elt_0_0", 0.883485346527455], + PARAMETER["elt_0_1", -0.468458794848877], + PARAMETER["elt_0_2", 3455869.17937689], + PARAMETER["elt_1_0", 0.468458794848877], + PARAMETER["elt_1_1", 0.883485346527455], + PARAMETER["elt_1_2", 5478710.88035753], + PARAMETER["elt_2_2", 1] + ] + */ + + var p = ((List<object>) parameters).Cast<Parameter>().ToList(); + var rowParam = p.FirstOrDefault(x => x.Name == "num_row"); + var colParam = p.FirstOrDefault(x => x.Name == "num_col"); + + if (rowParam == null) + { + throw new ArgumentNullException(nameof(rowParam), + "Affine transform does not contain 'num_row' parameter"); + } + + if (colParam == null) + { + throw new ArgumentNullException(nameof(colParam), + "Affine transform does not contain 'num_col' parameter"); + } + + int rowVal = (int) rowParam.Value; + int colVal = (int) colParam.Value; + + if (rowVal <= 0) + { + throw new ArgumentException("Affine transform contains invalid value of 'num_row' parameter"); + } + + if (colVal <= 0) + { + throw new ArgumentException("Affine transform contains invalid value of 'num_col' parameter"); + } + + //creates working matrix; + double[,] matrix = new double[rowVal, colVal]; + + //simply process matrix values - no elt_ROW_COL parsing + foreach (var param in p) + { + if (param == null || param.Name == null) + { + continue; + } + + switch (param.Name) + { + case "num_row": + case "num_col": + break; + case "elt_0_0": + matrix[0, 0] = param.Value; + break; + case "elt_0_1": + matrix[0, 1] = param.Value; + break; + case "elt_0_2": + matrix[0, 2] = param.Value; + break; + case "elt_0_3": + matrix[0, 3] = param.Value; + break; + case "elt_1_0": + matrix[1, 0] = param.Value; + break; + case "elt_1_1": + matrix[1, 1] = param.Value; + break; + case "elt_1_2": + matrix[1, 2] = param.Value; + break; + case "elt_1_3": + matrix[1, 3] = param.Value; + break; + case "elt_2_0": + matrix[2, 0] = param.Value; + break; + case "elt_2_1": + matrix[2, 1] = param.Value; + break; + case "elt_2_2": + matrix[2, 2] = param.Value; + break; + case "elt_2_3": + matrix[2, 3] = param.Value; + break; + case "elt_3_0": + matrix[3, 0] = param.Value; + break; + case "elt_3_1": + matrix[3, 1] = param.Value; + break; + case "elt_3_2": + matrix[3, 2] = param.Value; + break; + case "elt_3_3": + matrix[3, 3] = param.Value; + break; + } + } + + //use "matrix" constructor to create transformation matrix + return new AffineTransform(matrix); + } + + return null; + } + + public object BuildFittedCoordinateSystem(int offset, string keyword, char leftDelimiter, string name, object pmt, + object projcs, object authority, char rightDelimiter) + { + var baseCS = (ProjectedCoordinateSystem) projcs; + + var toBaseTransform = (MathTransform) pmt; + + var result = new FittedCoordinateSystem (baseCS, toBaseTransform, name, string.Empty, -1, string.Empty, string.Empty, string.Empty); + + if (authority is Authority authObj) + { + result.AuthorityCode = authObj.Code; + result.Authority = authObj.Name; + } + + + return result; + } + + + + public Result<char, CoordinateSystem> Parse(string wkt) + { + return Parser(wkt); + } + + + } +} diff --git a/src/ProjNet/Wkt/WktToProjConverter.cs b/src/ProjNet/Wkt/WktToProjConverter.cs new file mode 100644 index 0000000..a0b1b1a --- /dev/null +++ b/src/ProjNet/Wkt/WktToProjConverter.cs @@ -0,0 +1,567 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Pidgin; +using ProjNet.CoordinateSystems; +using ProjNet.CoordinateSystems.Transformations; +using ProjNet.IO.Wkt.Core; +using ProjNet.IO.Wkt.Tree; +using Unit = ProjNet.CoordinateSystems.Unit; + +namespace ProjNet.Wkt +{ + /// <summary> + /// WktToProjConverter - Visitor for converting Wkt to Proj objects. + /// </summary> + public class WktToProjConverter : IWktTraverseHandler + { + /// <summary> + /// Subclass for holding conversion results. + /// </summary> + public class WktToProjRecord + { + /// <summary> + /// The Wkt Object. + /// </summary> + public IWktObject WktObject { get; set; } + + /// <summary> + /// The Proj4Net Object. + /// </summary> + public object ProjObject { get; set; } + + /// <summary> + /// Constructor. + /// </summary> + /// <param name="wktObject"></param> + /// <param name="projObject"></param> + public WktToProjRecord(IWktObject wktObject, object projObject) + { + WktObject = wktObject; + ProjObject = projObject; + } + } + + private Dictionary<IWktObject, object> table = new Dictionary<IWktObject, object>(); + + private CoordinateSystemFactory factory; + + + /// <summary> + /// Constructor. + /// </summary> + /// <param name="factory"></param> + public WktToProjConverter(CoordinateSystemFactory factory = null) + { + this.factory = factory ?? new CoordinateSystemFactory(); + } + + + /// <summary> + /// Handler for WktAuthority. + /// </summary> + /// <param name="authority"></param> + public void Handle(WktAuthority authority) + { + //table.Add(new WktToProjRecord(authority, null)); + table[authority] = null; + } + + /// <summary> + /// Handler for WktAxis. + /// </summary> + /// <param name="axis"></param> + public void Handle(WktAxis axis) + { + var a = new AxisInfo(axis.Name, Map(axis.Direction)); + //table.Add(new WktToProjRecord(axis, a)); + table[axis] = a; + } + + /// <summary> + /// Handler for WktDatum. + /// </summary> + /// <param name="datum"></param> + public void Handle(WktDatum datum) + { + //table.Add(new WktToProjRecord(datum, null)); + table[datum] = null; + } + + /// <summary> + /// Handler for WktEllipsoid. + /// </summary> + /// <param name="ellipsoid"></param> + public void Handle(WktEllipsoid ellipsoid) + { + //table.Add(new WktToProjRecord(ellipsoid, null)); + table[ellipsoid] = null; + } + + /// <summary> + /// Handler for WktSpheroid. + /// </summary> + /// <param name="spheroid"></param> + public void Handle(WktSpheroid spheroid) + { + //table.Add(new WktToProjRecord(spheroid, null)); + table[spheroid] = null; + } + + /// <summary> + /// Handler for WktExtension. + /// </summary> + /// <param name="extension"></param> + public void Handle(WktExtension extension) + { + //table.Add(new WktToProjRecord(extension, null)); + table[extension] = null; + } + + /// <summary> + /// Handler for WktUnit. + /// </summary> + /// <param name="unit"></param> + public void Handle(WktUnit unit) + { + string authName = unit.Authority?.Name; + long? authCode = unit.Authority?.Code; + + var u = new Unit(unit.ConversionFactor, unit.Name, authName, authCode.GetValueOrDefault(), + string.Empty, string.Empty, string.Empty); + + //table.Add(new WktToProjRecord(unit, u)); + table[unit] = u; + } + + /// <inheritdoc/> + public void Handle(WktParameter parameter) + { + var p = new ProjectionParameter(parameter.Name, parameter.Value); + //table.Add(new WktToProjRecord(parameter, p)); + table[parameter] = p; + } + + /// <inheritdoc/> + public void Handle(WktParameterMathTransform pmt) + { + //table.Add(new WktToProjRecord(pmt, null)); + table[pmt] = null; + } + + /// <inheritdoc/> + public void Handle(WktPrimeMeridian meridian) + { + //table.Add(new WktToProjRecord(meridian, null)); + table[meridian] = null; + } + + /// <inheritdoc/> + public void Handle(WktProjection projection) + { + //table.Add(new WktToProjRecord(projection, null)); + table[projection] = null; + } + + /// <inheritdoc/> + public void Handle(WktToWgs84 toWgs84) + { + var wgs84 = new Wgs84ConversionInfo(toWgs84.DxShift, toWgs84.DyShift, toWgs84.DzShift, toWgs84.ExRotation, + toWgs84.EyRotation, toWgs84.EzRotation, toWgs84.PpmScaling, toWgs84.Description); + + //table.Add(new WktToProjRecord(toWgs84, wgs84));i + table[toWgs84] = wgs84; + } + + /// <inheritdoc/> + public void Handle(WktGeocentricCoordinateSystem cs) + { + //table.Add(new WktToProjRecord(cs, null)); + table[cs] = null; + } + + /// <inheritdoc/> + public void Handle(WktGeographicCoordinateSystem cs) + { + //table.Add(new WktToProjRecord(cs, null)); + table[cs] = null; + } + + /// <inheritdoc/> + public void Handle(WktProjectedCoordinateSystem cs) + { + //table.Add(new WktToProjRecord(cs, null)); + table[cs] = null; + } + + /// <inheritdoc/> + public void Handle(WktFittedCoordinateSystem cs) + { + //table.Add(new WktToProjRecord(cs, null)); + table[cs] = null; + } + + + /// <summary> + /// Convert WktObject(s) to Proj CoordinateSystem. + /// </summary> + /// <param name="wktObject"></param> + /// <returns></returns> + public CoordinateSystem Convert(IWktObject wktObject) + { + // Make sure the table is empty before starting... + table.Clear(); + + // Travel the tree and make sure above Handle methods are called. + wktObject.Traverse(this); + + // Recursive checking and filling the table... + return (CoordinateSystem)FindOrCreateProjObject(wktObject); + } + + private object FindOrCreateProjObject<T>(T wkt) + where T : IWktObject + { + if (wkt == null) + return null; + + object item = table[wkt];//.FirstOrDefault(x => x.WktObject.Equals(wkt)); + + // Check if already exists + if (item != null) + return item; + + // Fill in the gaps where projObject is null; + // Dispatch to Create methods... + if (wkt is WktProjectedCoordinateSystem projcs) + return Create(projcs); + else if (wkt is WktGeographicCoordinateSystem geogcs) + return Create(geogcs); + else if (wkt is WktGeocentricCoordinateSystem geoccs) + return Create(geoccs); + else if (wkt is WktFittedCoordinateSystem fitcs) + return Create(fitcs); + else if (wkt is WktParameterMathTransform pmt) + return Create(pmt); + else if (wkt is WktProjection prj) + return Create(prj); + else if (wkt is WktDatum d) + return Create(d); + else if (wkt is WktEllipsoid e) + return Create(e); + else if (wkt is WktPrimeMeridian pm) + return Create(pm); + + return null; + } + + private object Create(WktProjectedCoordinateSystem projcs) + { + var gcs = (GeographicCoordinateSystem)FindOrCreateProjObject(projcs.GeographicCoordinateSystem); + var p = (Projection) FindOrCreateProjObject(projcs.Projection); + + var u = (Unit) FindOrCreateProjObject(projcs.Unit); + var lu = (LinearUnit) null; + if (u != null) + { + lu = new LinearUnit(u.ConversionFactor, u.Name, u.Authority, u.AuthorityCode, string.Empty, + string.Empty, string.Empty); + } + + var ax1 = (AxisInfo) FindOrCreateProjObject(projcs.Axes.ElementAtOrDefault(0)); + ax1 = ax1 ?? new AxisInfo("X", AxisOrientationEnum.East); + + var ax2 = (AxisInfo) FindOrCreateProjObject(projcs.Axes.ElementAtOrDefault(1)); + ax2 = ax2 ?? new AxisInfo("Y", AxisOrientationEnum.North); + + var result = new ProjectedCoordinateSystem(gcs.HorizontalDatum, gcs, lu, p, new List<AxisInfo>{ax1, ax2}, projcs.Name, projcs.Authority?.Name, projcs.Authority!=null ? projcs.Authority.Code : -1, string.Empty, string.Empty, string.Empty); + + return FillItem(projcs, result); + } + + private object Create(WktGeographicCoordinateSystem geogcs) + { + var u = (Unit) FindOrCreateProjObject(geogcs.AngularUnit); + AngularUnit au = null; + if (u != null) + { + au = new AngularUnit(u.ConversionFactor, u.Name, u.Authority, u.AuthorityCode, string.Empty, + string.Empty, string.Empty); + } + + var d = (HorizontalDatum) FindOrCreateProjObject(geogcs.HorizontalDatum); + var pm = (PrimeMeridian) FindOrCreateProjObject(geogcs.PrimeMeridian); + + //This is default axis values if not specified. + var ax1 = (AxisInfo) FindOrCreateProjObject(geogcs.Axes.ElementAtOrDefault(0)); + ax1 = ax1 ?? new AxisInfo("Lon", AxisOrientationEnum.East); + var ax2 = (AxisInfo) FindOrCreateProjObject(geogcs.Axes.ElementAtOrDefault(1)); + ax2 = ax2 ?? new AxisInfo("Lat", AxisOrientationEnum.North); + + + var result = this.factory.CreateGeographicCoordinateSystem(geogcs.Name, angularUnit:au, datum: d, + primeMeridian: pm, axis0: ax1, axis1:ax2); + + if (geogcs.Authority != null) + { + result.Authority = geogcs.Authority.Name; + result.AuthorityCode = geogcs.Authority.Code; + } + + return FillItem(geogcs, result); + } + + + private object Create(WktFittedCoordinateSystem fitcs) + { + var baseCS = (ProjectedCoordinateSystem) FindOrCreateProjObject(fitcs.ProjectedCoordinateSystem); + + var toBaseTransform = (MathTransform) FindOrCreateProjObject(fitcs.ParameterMathTransform); + + var result = new FittedCoordinateSystem (baseCS, toBaseTransform, fitcs.Name, fitcs.Authority?.Name, fitcs.Authority!=null?fitcs.Authority.Code: -1, string.Empty, string.Empty, string.Empty); + + return FillItem(fitcs, result); + } + + private object Create(WktGeocentricCoordinateSystem geoccs) + { + var u = (Unit) FindOrCreateProjObject(geoccs.Unit); + var lu = (LinearUnit) null; + if (u != null) + { + lu = new LinearUnit(u.ConversionFactor, u.Name, u.Authority, u.AuthorityCode, string.Empty, + string.Empty, string.Empty); + } + + var d = (HorizontalDatum) FindOrCreateProjObject(geoccs.Datum); + var pm = (PrimeMeridian) FindOrCreateProjObject(geoccs.PrimeMeridian); + + var result = + this.factory.CreateGeocentricCoordinateSystem(geoccs.Name, datum: d, linearUnit: lu, primeMeridian: pm); + + if (geoccs.Authority != null) + { + result.Authority = geoccs.Authority.Name; + result.AuthorityCode = geoccs.Authority.Code; + } + + return FillItem(geoccs, result); + } + + private object Create(WktProjection wkt) + { + var pp = new List<ProjectionParameter>(); + // Projection parameters come after Projection in WKT + var subTable = table.Keys + .SkipWhile(x => !(x is WktParameter)) + .TakeWhile(x => x is WktParameter) + .ToList(); + + if (subTable.Any()) + { + foreach (var item in subTable) + { + var p = (ProjectionParameter) FindOrCreateProjObject(item); + pp.Add(p); + } + } + + /* + Factory method doesn't provide Authority and AuthorityCode and they are not settable afterward. + var result = + this.factory.CreateProjection(wkt.Name, string.Empty, pp); + */ + long ac = -1; + if (wkt.Authority != null) + { + ac = wkt.Authority.Code; + } + + var result = new Projection(wkt.Name, pp, wkt.Name, wkt.Authority?.Name, ac, string.Empty, + string.Empty, string.Empty); + + return FillItem(wkt, result); + } + + + private object Create(WktDatum wkt) + { + var wgs84 = (Wgs84ConversionInfo) FindOrCreateProjObject(wkt.ToWgs84); + var e = (Ellipsoid) FindOrCreateProjObject(wkt.Spheroid); + + var result = this.factory.CreateHorizontalDatum(wkt.Name, DatumType.HD_Geocentric, ellipsoid:e, toWgs84: wgs84); + + if (wkt.Authority != null) + { + result.Authority = wkt.Authority.Name; + result.AuthorityCode = wkt.Authority.Code; + } + + return FillItem(wkt, result); + } + + + private object Create(WktEllipsoid wkt) + { + var result = new Ellipsoid(wkt.SemiMajorAxis, 0.0, wkt.InverseFlattening, true, LinearUnit.Metre, wkt.Name, wkt.Authority?.Name, wkt.Authority!=null ? wkt.Authority.Code : -1, string.Empty, string.Empty, string.Empty); + + return FillItem(wkt, result); + } + + + private object Create(WktPrimeMeridian wkt) + { + var au = (AngularUnit) AngularUnit.Degrees; + + var result = this.factory.CreatePrimeMeridian(wkt.Name, angularUnit: au, longitude: wkt.Longitude); + + if (wkt.Authority != null) + { + result.Authority = wkt.Authority.Name; + result.AuthorityCode = wkt.Authority.Code; + } + + return FillItem(wkt, result); + } + + + private object FillItem(IWktObject wkt, object proj) + { + //int idx = table.FindIndex(x => x.WktObject.Equals(wkt)); + //table[idx].ProjObject = proj; + table[wkt] = proj; + return proj; + } + + private object Create(WktParameterMathTransform input) + { + if (input.Name.Equals("Affine", StringComparison.InvariantCultureIgnoreCase)) + { + /* + PARAM_MT[ + "Affine", + PARAMETER["num_row",3], + PARAMETER["num_col",3], + PARAMETER["elt_0_0", 0.883485346527455], + PARAMETER["elt_0_1", -0.468458794848877], + PARAMETER["elt_0_2", 3455869.17937689], + PARAMETER["elt_1_0", 0.468458794848877], + PARAMETER["elt_1_1", 0.883485346527455], + PARAMETER["elt_1_2", 5478710.88035753], + PARAMETER["elt_2_2", 1] + ] + */ + //tokenizer stands on the first PARAMETER + var paramInfo = input; + //manage required parameters - row, col + var rowParam = paramInfo.Parameters.FirstOrDefault(x => x.Name == "num_row"); + var colParam = paramInfo.Parameters.FirstOrDefault(x => x.Name == "num_col"); + + if (rowParam == null) + { + throw new ArgumentNullException(nameof(rowParam), + "Affine transform does not contain 'num_row' parameter"); + } + + if (colParam == null) + { + throw new ArgumentNullException(nameof(colParam), + "Affine transform does not contain 'num_col' parameter"); + } + + int rowVal = (int) rowParam.Value; + int colVal = (int) colParam.Value; + + if (rowVal <= 0) + { + throw new ArgumentException("Affine transform contains invalid value of 'num_row' parameter"); + } + + if (colVal <= 0) + { + throw new ArgumentException("Affine transform contains invalid value of 'num_col' parameter"); + } + + //creates working matrix; + double[,] matrix = new double[rowVal, colVal]; + + //simply process matrix values - no elt_ROW_COL parsing + foreach (var param in paramInfo.Parameters) + { + if (param == null || param.Name == null) + { + continue; + } + + switch (param.Name) + { + case "num_row": + case "num_col": + break; + case "elt_0_0": + matrix[0, 0] = param.Value; + break; + case "elt_0_1": + matrix[0, 1] = param.Value; + break; + case "elt_0_2": + matrix[0, 2] = param.Value; + break; + case "elt_0_3": + matrix[0, 3] = param.Value; + break; + case "elt_1_0": + matrix[1, 0] = param.Value; + break; + case "elt_1_1": + matrix[1, 1] = param.Value; + break; + case "elt_1_2": + matrix[1, 2] = param.Value; + break; + case "elt_1_3": + matrix[1, 3] = param.Value; + break; + case "elt_2_0": + matrix[2, 0] = param.Value; + break; + case "elt_2_1": + matrix[2, 1] = param.Value; + break; + case "elt_2_2": + matrix[2, 2] = param.Value; + break; + case "elt_2_3": + matrix[2, 3] = param.Value; + break; + case "elt_3_0": + matrix[3, 0] = param.Value; + break; + case "elt_3_1": + matrix[3, 1] = param.Value; + break; + case "elt_3_2": + matrix[3, 2] = param.Value; + break; + case "elt_3_3": + matrix[3, 3] = param.Value; + break; + } + } + + //use "matrix" constructor to create transformation matrix + return new AffineTransform(matrix); + } + + return null; + } + + + + private AxisOrientationEnum Map(WktAxisDirectionEnum input) + { + // Numbers are equal so cast must succeed + return (AxisOrientationEnum) input; + } + } +} diff --git a/test/ProjNet.Tests/CoordinateTransformTests.cs b/test/ProjNet.Tests/CoordinateTransformTests.cs index cbfaea6..c5b3b93 100644 --- a/test/ProjNet.Tests/CoordinateTransformTests.cs +++ b/test/ProjNet.Tests/CoordinateTransformTests.cs @@ -17,7 +17,7 @@ public CoordinateTransformTests() { Verbose = true; } - + [Test] public void TestTransformListOfCoordinates() { @@ -98,7 +98,7 @@ public void TestCentralMeridianParse() var pSouthPole = pCoordSysFactory.CreateFromWkt(strSouthPole); Assert.IsNotNull(pSouthPole); } - + [Test] public void TestAlbersProjection() { @@ -516,7 +516,7 @@ public void TestKrovak_Greenwich_Projection() var gcsKrovak = CoordinateSystemFactory.CreateGeographicCoordinateSystem("Bessel 1840", AngularUnit.Degrees, datum, PrimeMeridian.Greenwich, new AxisInfo("Lon", AxisOrientationEnum.East), new AxisInfo("Lat", AxisOrientationEnum.North)); - + var parameters = new List<ProjectionParameter>(5) { new ProjectionParameter("latitude_of_center", 49.5), @@ -742,6 +742,7 @@ public void TestAustralianAntarcticPolarStereographicProjection() var cs1 = CoordinateSystemFactory.CreateFromWkt(wkt4326); var cs2 = CoordinateSystemFactory.CreateFromWkt(wkt3032); + var ctf = new CoordinateTransformationFactory(); var ict = ctf.CreateFromCoordinateSystems(cs2, cs1); @@ -766,7 +767,7 @@ public void TestUnitTransforms() //var expected = new[] { 708066.19058, 1151461.51413 }; double[] expected = new[] { 708066.19057935325, 1151426.4460563776 }; - + double[] p1 = trans.MathTransform.Transform(p0); double[] p2 = trans.MathTransform.Inverse().Transform(p1); @@ -804,15 +805,15 @@ public void TestCassiniSoldner() var csTarget = CoordinateSystemFactory.CreateFromWkt( "PROJCS[\"DHDN / Soldner Berlin\",GEOGCS[\"DHDN\",DATUM[\"Deutsches_Hauptdreiecksnetz\",SPHEROID[\"Bessel 1841\",6377397.155,299.1528128,AUTHORITY[\"EPSG\",\"7004\"]],TOWGS84[598.1,73.7,418.2,0.202,0.045,-2.455,6.7],AUTHORITY[\"EPSG\",\"6314\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4314\"]],PROJECTION[\"Cassini_Soldner\"],PARAMETER[\"latitude_of_origin\",52.41864827777778],PARAMETER[\"central_meridian\",13.62720366666667],PARAMETER[\"false_easting\",40000],PARAMETER[\"false_northing\",10000],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],AXIS[\"x\",NORTH],AXIS[\"y\",EAST],AUTHORITY[\"EPSG\",\"3068\"]]"); - Test("CassiniSoldner", csSource, csTarget, - new[] { 13.408055555556, 52.518611111111 }, + Test("CassiniSoldner", csSource, csTarget, + new[] { 13.408055555556, 52.518611111111 }, new[] { 25244.540, 21300.969 }, 0.3, 1.0E-5); /* var ct = CoordinateTransformationFactory.CreateFromCoordinateSystems(csSource, csTarget); var pgeo = new[] {13.408055555556, 52.518611111111}; var pcs = ct.MathTransform.Transform(pgeo); - + //Evaluated using DotSpatial.Projections var pcsExpected = new[] {25244.540, 21300.969}; @@ -891,7 +892,7 @@ public void AffineTransformationTest () // 3) Scale: 1.0 //TODO MathTransformFactory fac = new MathTransformFactory (); - double[,] matrix = new double[,] {{0.883485346527455, -0.468458794848877, 3455869.17937689}, + double[,] matrix = new double[,] {{0.883485346527455, -0.468458794848877, 3455869.17937689}, {0.468458794848877, 0.883485346527455, 5478710.88035753}, {0.0 , 0.0, 1},}; var mt = new AffineTransform (matrix); @@ -925,7 +926,7 @@ public void InverseAffineTransformationTest () // 3) Scale: 1.0 //TODO MathTransformFactory fac = new MathTransformFactory (); - double[,] matrix = new double[,] {{0.883485346527455, -0.468458794848877, 3455869.17937689}, + double[,] matrix = new double[,] {{0.883485346527455, -0.468458794848877, 3455869.17937689}, {0.468458794848877, 0.883485346527455, 5478710.88035753}, {0.0 , 0.0, 1},}; var mt = new AffineTransform (matrix); @@ -975,27 +976,27 @@ public void TestTransformOnFittedCoordinateSystem () // 2)Rotation: 332,0657, Rotation point X=3456926,640m Y=5481071,278m; // 3) Scale: 1.0 - string ft_wkt = "FITTED_CS[\"Local coordinate system MNAU (based on Gauss-Krueger)\"," + - "PARAM_MT[\"Affine\"," + - "PARAMETER[\"num_row\",3],PARAMETER[\"num_col\",3],PARAMETER[\"elt_0_0\", 0.883485346527455],PARAMETER[\"elt_0_1\", -0.468458794848877],PARAMETER[\"elt_0_2\", 3455869.17937689],PARAMETER[\"elt_1_0\", 0.468458794848877],PARAMETER[\"elt_1_1\", 0.883485346527455],PARAMETER[\"elt_1_2\", 5478710.88035753],PARAMETER[\"elt_2_2\", 1]]," + - "PROJCS[\"DHDN / Gauss-Kruger zone 3\"," + - "GEOGCS[\"DHDN\"," + - "DATUM[\"Deutsches_Hauptdreiecksnetz\"," + - "SPHEROID[\"Bessel 1841\", 6377397.155, 299.1528128, AUTHORITY[\"EPSG\", \"7004\"]]," + - "TOWGS84[612.4, 77, 440.2, -0.054, 0.057, -2.797, 0.525975255930096]," + - "AUTHORITY[\"EPSG\", \"6314\"]]," + - "PRIMEM[\"Greenwich\", 0, AUTHORITY[\"EPSG\", \"8901\"]]," + - "UNIT[\"degree\", 0.0174532925199433, AUTHORITY[\"EPSG\", \"9122\"]]," + - "AUTHORITY[\"EPSG\", \"4314\"]]," + - "PROJECTION[\"Transverse_Mercator\"]," + - "PARAMETER[\"latitude_of_origin\", 0]," + - "PARAMETER[\"central_meridian\", 9]," + - "PARAMETER[\"scale_factor\", 1]," + - "PARAMETER[\"false_easting\", 3500000]," + - "PARAMETER[\"false_northing\", 0]," + - "UNIT[\"metre\", 1, AUTHORITY[\"EPSG\", \"9001\"]]," + - "AUTHORITY[\"EPSG\", \"31467\"]]" + - "]"; + string ft_wkt = "FITTED_CS[\"Local coordinate system MNAU (based on Gauss-Krueger)\",\n" + + "PARAM_MT[\"Affine\",\n" + + "PARAMETER[\"num_row\",3],PARAMETER[\"num_col\",3],PARAMETER[\"elt_0_0\", 0.883485346527455],PARAMETER[\"elt_0_1\", -0.468458794848877],PARAMETER[\"elt_0_2\", 3455869.17937689],PARAMETER[\"elt_1_0\", 0.468458794848877],PARAMETER[\"elt_1_1\", 0.883485346527455],PARAMETER[\"elt_1_2\", 5478710.88035753],PARAMETER[\"elt_2_2\", 1]],\n" + + "PROJCS[\"DHDN / Gauss-Kruger zone 3\",\n" + + "GEOGCS[\"DHDN\",\n" + + "DATUM[\"Deutsches_Hauptdreiecksnetz\",\n" + + "SPHEROID[\"Bessel 1841\", 6377397.155, 299.1528128, AUTHORITY[\"EPSG\", \"7004\"]],\n" + + "TOWGS84[612.4, 77, 440.2, -0.054, 0.057, -2.797, 0.525975255930096],\n" + + "AUTHORITY[\"EPSG\", \"6314\"]],\n" + + "PRIMEM[\"Greenwich\", 0, AUTHORITY[\"EPSG\", \"8901\"]],\n" + + "UNIT[\"degree\", 0.0174532925199433, AUTHORITY[\"EPSG\", \"9122\"]],\n" + + "AUTHORITY[\"EPSG\", \"4314\"]],\n" + + "PROJECTION[\"Transverse_Mercator\"],\n" + + "PARAMETER[\"latitude_of_origin\", 0],\n" + + "PARAMETER[\"central_meridian\", 9],\n" + + "PARAMETER[\"scale_factor\", 1],\n" + + "PARAMETER[\"false_easting\", 3500000],\n" + + "PARAMETER[\"false_northing\", 0],\n" + + "UNIT[\"metre\", 1, AUTHORITY[\"EPSG\", \"9001\"]],\n" + + "AUTHORITY[\"EPSG\", \"31467\"]]\n" + + "]"; //string gk_wkt = "PROJCS[\"DHDN / Gauss-Kruger zone 3\",GEOGCS[\"DHDN\",DATUM[\"Deutsches_Hauptdreiecksnetz\",SPHEROID[\"Bessel 1841\",6377397.155,299.1528128,AUTHORITY[\"EPSG\",\"7004\"]],AUTHORITY[\"EPSG\",\"6314\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.01745329251994328,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4314\"]],PROJECTION[\"Transverse_Mercator\"],PARAMETER[\"latitude_of_origin\",0],PARAMETER[\"central_meridian\",9],PARAMETER[\"scale_factor\",1],PARAMETER[\"false_easting\",3500000],PARAMETER[\"false_northing\",0],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],AUTHORITY[\"EPSG\",\"31467\"]]"; diff --git a/test/ProjNet.Tests/ProjNET.Tests.csproj b/test/ProjNet.Tests/ProjNET.Tests.csproj index 610cb67..00ca9b7 100644 --- a/test/ProjNet.Tests/ProjNET.Tests.csproj +++ b/test/ProjNet.Tests/ProjNET.Tests.csproj @@ -20,4 +20,8 @@ <EmbeddedResource Include="SRID.csv" /> </ItemGroup> + <ItemGroup> + <Compile Remove="WKT\WktToProjConverterTests.cs" /> + </ItemGroup> + </Project> diff --git a/test/ProjNet.Tests/ProjNetIssues.cs b/test/ProjNet.Tests/ProjNetIssues.cs index 3234c8c..df8e5b3 100644 --- a/test/ProjNet.Tests/ProjNetIssues.cs +++ b/test/ProjNet.Tests/ProjNetIssues.cs @@ -171,9 +171,9 @@ public void TestNtsIssue191() Assert.That(projection, Is.Not.Null); var wgs84 = GeographicCoordinateSystem.WGS84; - var dummy = factory.CreateProjectedCoordinateSystem("dummy pcs", - wgs84, projection, LinearUnit.Metre, - new AxisInfo("X", AxisOrientationEnum.East), + var dummy = factory.CreateProjectedCoordinateSystem("dummy pcs", + wgs84, projection, LinearUnit.Metre, + new AxisInfo("X", AxisOrientationEnum.East), new AxisInfo("Y", AxisOrientationEnum.North)); Assert.That(dummy, Is.Not.Null); @@ -234,7 +234,7 @@ public void TestGitHubIssue53() Assert.AreEqual(point[1], rBack[1], 1e-5); } - [Test, Description("Coordinate system isn't supported"), Category("Issue")] + [Test, Description("Coordinate system isn't supported"), Category("Issue"), Ignore("Currently parser Partial match isn't supported!")] public void TestGitHubIssue98() { var cs = CoordinateSystemFactory.CreateFromWkt( @@ -266,7 +266,7 @@ public void TestGitHubIssue98() ], UNIT[""degree"",0.0174532925199433, AUTHORITY[""EPSG"",""9122""]], AUTHORITY[""EPSG"",""4619""] - ], + ], PROJECTION[""Transverse_Mercator""], PARAMETER[""latitude_of_origin"",0], PARAMETER[""central_meridian"",18], diff --git a/test/ProjNet.Tests/SharpMapIssues.cs b/test/ProjNet.Tests/SharpMapIssues.cs index 305ab64..3c4874a 100644 --- a/test/ProjNet.Tests/SharpMapIssues.cs +++ b/test/ProjNet.Tests/SharpMapIssues.cs @@ -12,16 +12,28 @@ public SharpMapIssues() Verbose = true; } - string wkt2236 = "PROJCS[\"NAD83 / Florida East (ftUS)\", GEOGCS [ \"NAD83\", DATUM [\"North American Datum 1983 (EPSG ID 6269)\", SPHEROID [\"GRS 1980 (EPSG ID 7019)\", 6378137, 298.257222101]], PRIMEM [ \"Greenwich\", 0.000000 ], UNIT [\"Decimal Degree\", 0.01745329251994328]], PROJECTION [\"SPCS83 Florida East zone (US Survey feet) (EPSG OP 15318)\"], PARAMETER [\"Latitude_Of_Origin\", 24.33333333333333333333333333333333333333], PARAMETER [\"Central_Meridian\", -80.9999999999999999999999999999999999999], PARAMETER [\"Scale_Factor\", 0.999941177], PARAMETER [\"False_Easting\", 656166.6669999999999999999999999999999999], PARAMETER [\"False_Northing\", 0], UNIT [\"U.S. Foot\", 0.3048006096012192024384048768097536195072]]"; + string wkt2236 = "PROJCS[\"NAD83 / Florida East (ftUS)\", \n" + + " GEOGCS [ \"NAD83\",\n" + + " DATUM [\"North American Datum 1983 (EPSG ID 6269)\", \n" + + " SPHEROID [\"GRS 1980 (EPSG ID 7019)\", 6378137, 298.257222101]], \n" + + " PRIMEM [ \"Greenwich\", 0.000000 ], " + + " UNIT [\"Decimal Degree\", 0.01745329251994328]], \n" + + " PROJECTION [\"SPCS83 Florida East zone (US Survey feet) (EPSG OP 15318)\"], \n" + + " PARAMETER [\"Latitude_Of_Origin\", 24.33333333333333333333333333333333333333], \n" + + " PARAMETER [\"Central_Meridian\", -80.9999999999999999999999999999999999999], \n" + + " PARAMETER [\"Scale_Factor\", 0.999941177], \n" + + " PARAMETER [\"False_Easting\", 656166.6669999999999999999999999999999999], \n" + + " PARAMETER [\"False_Northing\", 0], \n" + + " UNIT [\"U.S. Foot\", 0.3048006096012192024384048768097536195072]]"; string wkt8307 = "GEOGCS [ \"WGS 84\", DATUM [\"World Geodetic System 1984 (EPSG ID 6326)\", SPHEROID [\"WGS 84 (EPSG ID 7030)\", 6378137, 298.257223563]], PRIMEM [ \"Greenwich\", 0.000000 ], UNIT [\"Decimal Degree\", 0.01745329251994328]]"; - [Test, Description("NAD83 (State Plane) projection to the WGS84 (Lat/Long), http://sharpmap.codeplex.com/discussions/435794")] + [Test, Description("NAD83 (State Plane) projection to the WGS84 (Lat/Long), http://sharpmap.codeplex.com/discussions/435794")] public void TestNad83ToWGS84() { var src = CoordinateSystemFactory.CreateFromWkt(wkt2236); var tgt = CoordinateSystemFactory.CreateFromWkt(wkt8307);//CoordinateSystems.GeographicCoordinateSystem.WGS84;; - ProjNet.CoordinateSystems.Projections.ProjectionsRegistry.Register("SPCS83 Florida East zone (US Survey feet) (EPSG OP 15318)", + ProjNet.CoordinateSystems.Projections.ProjectionsRegistry.Register("SPCS83 Florida East zone (US Survey feet) (EPSG OP 15318)", ReflectType("ProjNet.CoordinateSystems.Projections.TransverseMercator")); ICoordinateTransformation transform = null; diff --git a/test/ProjNet.Tests/WKT/WKTCoordSysParserTests.cs b/test/ProjNet.Tests/WKT/WKTCoordSysParserTests.cs index bb9a47a..ef36971 100644 --- a/test/ProjNet.Tests/WKT/WKTCoordSysParserTests.cs +++ b/test/ProjNet.Tests/WKT/WKTCoordSysParserTests.cs @@ -103,7 +103,7 @@ public void TestProjectedCoordinateSystem_EPSG_2918() { Tuple.Create("standard_parallel_1", 31.883333333333), Tuple.Create("standard_parallel_2", 30.1166666667), - Tuple.Create("latitude_of_origin", 29.6666666667), + Tuple.Create("latitude_of_origin", 29.6666666667), Tuple.Create("central_meridian", -100.333333333333), Tuple.Create("false_easting", 2296583.333), Tuple.Create("false_northing", 9842500d) @@ -219,7 +219,7 @@ public void TestCreateCoordinateTransformationForWktInCsv() foreach (string fp in failedProjections) { Console.WriteLine($"case \"{fp}\":"); - + } } @@ -347,30 +347,30 @@ public void TestFittedCoordinateSystemWkt () { var fac = new CoordinateSystemFactory (); FittedCoordinateSystem fcs = null; - string wkt = "FITTED_CS[\"Local coordinate system MNAU (based on Gauss-Krueger)\"," + - "PARAM_MT[\"Affine\"," + - "PARAMETER[\"num_row\",3],PARAMETER[\"num_col\",3],PARAMETER[\"elt_0_0\", 0.883485346527455],PARAMETER[\"elt_0_1\", -0.468458794848877],PARAMETER[\"elt_0_2\", 3455869.17937689],PARAMETER[\"elt_1_0\", 0.468458794848877],PARAMETER[\"elt_1_1\", 0.883485346527455],PARAMETER[\"elt_1_2\", 5478710.88035753],PARAMETER[\"elt_2_2\", 1]]," + + string wkt = "FITTED_CS[\"Local coordinate system MNAU (based on Gauss-Krueger)\"," + + "PARAM_MT[\"Affine\"," + + "PARAMETER[\"num_row\",3],PARAMETER[\"num_col\",3],PARAMETER[\"elt_0_0\", 0.883485346527455],PARAMETER[\"elt_0_1\", -0.468458794848877],PARAMETER[\"elt_0_2\", 3455869.17937689],PARAMETER[\"elt_1_0\", 0.468458794848877],PARAMETER[\"elt_1_1\", 0.883485346527455],PARAMETER[\"elt_1_2\", 5478710.88035753],PARAMETER[\"elt_2_2\", 1]]," + "PROJCS[\"DHDN / Gauss-Kruger zone 3\"," + - "GEOGCS[\"DHDN\"," + - "DATUM[\"Deutsches_Hauptdreiecksnetz\"," + - "SPHEROID[\"Bessel 1841\", 6377397.155, 299.1528128, AUTHORITY[\"EPSG\", \"7004\"]]," + - "TOWGS84[612.4, 77, 440.2, -0.054, 0.057, -2.797, 0.525975255930096]," + - "AUTHORITY[\"EPSG\", \"6314\"]]," + - "PRIMEM[\"Greenwich\", 0, AUTHORITY[\"EPSG\", \"8901\"]]," + - "UNIT[\"degree\", 0.0174532925199433, AUTHORITY[\"EPSG\", \"9122\"]]," + - "AUTHORITY[\"EPSG\", \"4314\"]]," + - "PROJECTION[\"Transverse_Mercator\"]," + - "PARAMETER[\"latitude_of_origin\", 0]," + - "PARAMETER[\"central_meridian\", 9]," + - "PARAMETER[\"scale_factor\", 1]," + + "GEOGCS[\"DHDN\"," + + "DATUM[\"Deutsches_Hauptdreiecksnetz\"," + + "SPHEROID[\"Bessel 1841\", 6377397.155, 299.1528128, AUTHORITY[\"EPSG\", \"7004\"]]," + + "TOWGS84[612.4, 77, 440.2, -0.054, 0.057, -2.797, 0.525975255930096]," + + "AUTHORITY[\"EPSG\", \"6314\"]]," + + "PRIMEM[\"Greenwich\", 0, AUTHORITY[\"EPSG\", \"8901\"]]," + + "UNIT[\"degree\", 0.0174532925199433, AUTHORITY[\"EPSG\", \"9122\"]]," + + "AUTHORITY[\"EPSG\", \"4314\"]]," + + "PROJECTION[\"Transverse_Mercator\"]," + + "PARAMETER[\"latitude_of_origin\", 0]," + + "PARAMETER[\"central_meridian\", 9]," + + "PARAMETER[\"scale_factor\", 1]," + "PARAMETER[\"false_easting\", 3500000]," + - "PARAMETER[\"false_northing\", 0]," + - "UNIT[\"metre\", 1, AUTHORITY[\"EPSG\", \"9001\"]]," + - "AUTHORITY[\"EPSG\", \"31467\"]]" + + "PARAMETER[\"false_northing\", 0]," + + "UNIT[\"metre\", 1, AUTHORITY[\"EPSG\", \"9001\"]]," + + "AUTHORITY[\"EPSG\", \"31467\"]]" + "]"; try - { + { fcs = fac.CreateFromWkt (wkt) as FittedCoordinateSystem; } catch (Exception ex) diff --git a/test/ProjNet.Tests/WKT/WktTextReaderTests.cs b/test/ProjNet.Tests/WKT/WktTextReaderTests.cs new file mode 100644 index 0000000..bf12c50 --- /dev/null +++ b/test/ProjNet.Tests/WKT/WktTextReaderTests.cs @@ -0,0 +1,683 @@ +using System; +using System.IO; +using System.Linq; +using System.Reflection.Metadata; +using NUnit.Framework; +using NUnit.Framework.Internal.Commands; +using Pidgin; +using ProjNet.CoordinateSystems; +using ProjNet.IO.Wkt.Core; +using ProjNet.IO.Wkt.Tree; +using ProjNET.Tests; +using ProjNet.Wkt; + +namespace ProjNet.Tests.Wkt; + +public class WktTextReaderTests +{ + + [Test] + public void TestGeogCsParser() + { + // Arrange + string parseText01 = "GEOGCS[\"NAD83(HARN)\", \n" + + "DATUM[\"NAD83_High_Accuracy_Regional_Network\", \n" + + "SPHEROID[\"GRS 1980\", 6378137, 298.257222101, AUTHORITY[\"EPSG\", \"7019\"]], \n" + + "TOWGS84[725, 685, 536, 0, 0, 0, 0], AUTHORITY[\"EPSG\", \"6152\"]], \n" + + "PRIMEM[\"Greenwich\", 0, AUTHORITY[\"EPSG\", \"8901\"]], \n" + + "UNIT[\"degree\", 0.0174532925199433, AUTHORITY[\"EPSG\", \"9122\"]], \n" + + "AUTHORITY[\"EPSG\", \"4152\"]]"; + + // Act + var presult = builder.Parse(parseText01); + var cs = (GeographicCoordinateSystem) presult.Value; + + // Assert + Assert.That(cs.Name, Is.EqualTo("NAD83(HARN)")); + } + + + [Test] + public void TestCoordinateSystem_EPSG_2918() + { + string parseText01 = + "PROJCS[\"NAD83(HARN) / Texas Central (ftUS)\", \n" + + "GEOGCS[\"NAD83(HARN)\", \n" + + "DATUM[\"NAD83_High_Accuracy_Regional_Network\", \n" + + "SPHEROID[\"GRS 1980\", 6378137, 298.257222101, AUTHORITY[\"EPSG\", \"7019\"]], \n" + + "TOWGS84[725, 685, 536, 0, 0, 0, 0], AUTHORITY[\"EPSG\", \"6152\"]], \n" + + "PRIMEM[\"Greenwich\", 0, AUTHORITY[\"EPSG\", \"8901\"]], \n" + + "UNIT[\"degree\", 0.0174532925199433, AUTHORITY[\"EPSG\", \"9122\"]], \n" + + "AUTHORITY[\"EPSG\", \"4152\"]], \n" + + "PROJECTION[\"Lambert_Conformal_Conic_2SP\"], \n" + + "PARAMETER[\"standard_parallel_1\", 31.883333333333], \n" + + "PARAMETER[\"standard_parallel_2\", 30.1166666667], \n" + + "PARAMETER[\"latitude_of_origin\", 29.6666666667], \n" + + "PARAMETER[\"central_meridian\", -100.333333333333], \n" + + "PARAMETER[\"false_easting\", 2296583.333], \n" + + "PARAMETER[\"false_northing\", 9842500], \n" + + "UNIT[\"US survey foot\", 0.304800609601219, AUTHORITY[\"EPSG\", \"9003\"]], \n" + + "AUTHORITY[\"EPSG\", \"2918\"]]"; + + var presult = builder.Parse(parseText01); + + // Assert + Assert.NotNull(presult); + Assert.That(presult.Success, Is.True); + + var result = (ProjectedCoordinateSystem)presult.Value; + Assert.NotNull(result); + Assert.That(result.Name, Is.EqualTo("NAD83(HARN) / Texas Central (ftUS)")); + + Assert.That(result.LinearUnit.Name, Is.EqualTo("US survey foot")); + Assert.That(result.LinearUnit.MetersPerUnit, Is.EqualTo(0.304800609601219)); + Assert.That(result.LinearUnit.Authority, Is.EqualTo("EPSG")); + Assert.That(result.LinearUnit.AuthorityCode, Is.EqualTo(9003)); + + var parameter = result.Projection.GetParameter("central_meridian"); + Assert.That(parameter.Name, Is.EqualTo("central_meridian")); + Assert.That(parameter.Value, Is.EqualTo(-100.333333333333)); + + Assert.That(result.Authority, Is.EqualTo("EPSG")); + Assert.That(result.AuthorityCode, Is.EqualTo(2918)); + } + + + [Test] + public void TestCoordinateSystem_EPSG_3067() + { + string parseText01 = + "PROJCS[\"ETRS89 / TM35FIN(E,N)\"," + + " GEOGCS[\"ETRS89\"," + + " DATUM[\"European_Terrestrial_Reference_System_1989\"," + + " SPHEROID[\"GRS 1980\",6378137,298.257222101,AUTHORITY[\"EPSG\",\"7019\"]]," + + " TOWGS84[0,0,0,0,0,0,0]," + + " AUTHORITY[\"EPSG\",\"6258\"]]," + + " PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]]," + + " UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]]," + + " AUTHORITY[\"EPSG\",\"4258\"]]," + + " PROJECTION[\"Transverse_Mercator\"]," + + " PARAMETER[\"latitude_of_origin\",0]," + + " PARAMETER[\"central_meridian\",27]," + + " PARAMETER[\"scale_factor\",0.9996]," + + " PARAMETER[\"false_easting\",500000]," + + " PARAMETER[\"false_northing\",0]," + + " UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]]," + + " AXIS[\"Easting\",EAST]," + + " AXIS[\"Northing\",NORTH]," + + " AUTHORITY[\"EPSG\",\"3067\"]]"; + + var presult = builder.Parse(parseText01); + + // Assert + Assert.NotNull(presult); + Assert.That(presult.Success, Is.True); + } + + + [Test] + public void TestCoordinateSystem_EPSG_3822() + { + string parseText01 = + "GEOCCS[\"TWD97\",\n" + + " DATUM[\"Taiwan_Datum_1997\",\n" + + " SPHEROID[\"GRS 1980\",6378137,298.257222101,AUTHORITY[\"EPSG\",\"7019\"]],\n" + + " AUTHORITY[\"EPSG\",\"1026\"]],\n" + + " PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],\n" + + " UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],\n" + + " AXIS[\"Geocentric X\",OTHER],\n" + + " AXIS[\"Geocentric Y\",OTHER],\n" + + " AXIS[\"Geocentric Z\",NORTH],\n" + + " AUTHORITY[\"EPSG\",\"3822\"]]"; + + var result = builder.Parse(parseText01); + + // Assert + Assert.NotNull(result); + Assert.That(result.Success, Is.True); + } + + [Test] + public void TestCoordinateSystem_EPSG_8351() + { + string parseText01 = + "GEOGCS[\"S-JTSK [JTSK03]\",DATUM[\"System_of_the_Unified_Trigonometrical_Cadastral_Network_JTSK03\",SPHEROID[\"Bessel 1841\",6377397.155,299.1528128,AUTHORITY[\"EPSG\",\"7004\"]],TOWGS84[485.021,169.465,483.839,7.786342,4.397554,4.102655,0],AUTHORITY[\"EPSG\",\"1201\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\",EAST],AUTHORITY[\"EPSG\",\"8351\"]]"; + + var result = builder.Parse(parseText01); + + // Assert + Assert.NotNull(result); + Assert.That(result.Success, Is.True); + } + + [Test] + public void TestCoordinateSystem_EPSG_32161() + { + string parseText01 = + "PROJCS[\"NAD83 / Puerto Rico & Virgin Is.\",\n" + + " GEOGCS[\"NAD83\",\n" + + " DATUM[\"North_American_Datum_1983\",\n" + + " SPHEROID[\"GRS 1980\",6378137,298.257222101,AUTHORITY[\"EPSG\",\"7019\"]],\n" + + " TOWGS84[0,0,0,0,0,0,0],\n" + + " AUTHORITY[\"EPSG\",\"6269\"]],\n" + + " PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],\n" + + " UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],\n" + + " AUTHORITY[\"EPSG\",\"4269\"]],\n" + + " PROJECTION[\"Lambert_Conformal_Conic_2SP\"],\n" + + " PARAMETER[\"standard_parallel_1\",18.43333333333333],\n" + + " PARAMETER[\"standard_parallel_2\",18.03333333333333],\n" + + " PARAMETER[\"latitude_of_origin\",17.83333333333333],\n" + + " PARAMETER[\"central_meridian\",-66.43333333333334],\n" + + " PARAMETER[\"false_easting\",200000],\n" + + " PARAMETER[\"false_northing\",200000],\n" + + " UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],\n" + + " AXIS[\"X\",EAST],\n" + + " AXIS[\"Y\",NORTH],\n" + + " AUTHORITY[\"EPSG\",\"32161\"]]"; + + var result = builder.Parse(parseText01); + + // Assert + Assert.NotNull(result); + Assert.That(result.Success, Is.True); + } + + [Test] + public void TestCoordinateSystem_EPSG_102100() + { + string parseText01 = + "PROJCS[\"WGS_1984_Web_Mercator_Auxiliary_Sphere (deprecated)\",\n" + + " GEOGCS[\"WGS 84\",\n" + + " DATUM[\"WGS_1984\",\n" + + " SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],\n" + + " AUTHORITY[\"EPSG\",\"6326\"]],\n" + + " PRIMEM[\"Greenwich\",0],\n" + + " UNIT[\"Degree\",0.0174532925199433]],\n" + + " PROJECTION[\"Mercator_1SP\"],\n" + + " PARAMETER[\"central_meridian\",0],\n" + + "PARAMETER[\"scale_factor\",1],\n" + + "PARAMETER[\"false_easting\",0],\n" + + "PARAMETER[\"false_northing\",0],\n" + + "UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],\n" + + "AXIS[\"Easting\",EAST],\n" + + "AXIS[\"Northing\",NORTH],\n" + + "EXTENSION[\"PROJ4\",\"+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs\"],\n" + + "AUTHORITY[\"ESRI\",\"102100\"]]"; + + var result = builder.Parse(parseText01); + + // Assert + Assert.NotNull(result); + } + + [Test] + public void TestCoordinateSystem_ESRI_4305() + { + string parseText01 = "GEOGCS[\"GCS_Voirol_Unifie_1960 (deprecated)\",\n" + + " DATUM[\"D_Voirol_Unifie_1960\",\n" + + " SPHEROID[\"Clarke 1880 (RGS)\",6378249.145,293.465,AUTHORITY[\"EPSG\",\"7012\"]],\n" + + " AUTHORITY[\"ESRI\",\"106011\"]],\n" + + " PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],\n" + + " UNIT[\"grad\",0.0157079632679489,AUTHORITY[\"EPSG\",\"9105\"]],\n" + + " AXIS[\"Latitude\",NORTH],\n" + + " AXIS[\"Longitude\",EAST],\n" + + " AUTHORITY[\"ESRI\",\"4305\"]]"; + + // Act + var result = builder.Parse(parseText01); + + // Assert + Assert.NotNull(result); + } + + /* + [Test] + public void ParseAllWKTs() + { + int parseCount = 0; + foreach (var wkt in SRIDReader.GetSrids()) + { + // Parse in from CSV... + using var sr = new StringReader(wkt.Wkt); + using var reader = new WktToProjTextReader(sr); + var result01 = reader.ReadToEnd(); + Assert.That(result01.Success, Is.True); + + var cs01 = result01.Value; + Assert.IsNotNull(cs01, "Could not parse WKT: " + wkt.Wkt); + + // Generate WKT string from WktObject... + var formatter = new DefaultWktOutputFormatter( + newline: Environment.NewLine, + leftDelimiter: '(', rightDelimiter: ')', + indent: "\t", extraWhitespace: " "); + string wkt01 = cs01.ToString(formatter); + + Assert.IsFalse(string.IsNullOrWhiteSpace(wkt01)); + + // Reparse the formatted WKT for extra testing... + using var sr02 = new StringReader(wkt01); + using var reader02 = new WktToProjTextReader(sr02); + var result02 = reader02.ReadToEnd(); + + WktCoordinateSystem cs02 = null; + if (!result02.Success) + { + Console.WriteLine($"Original: {wkt.Wkt}"); + Console.WriteLine($"Parsed 1: {wkt01}"); + Assert.Fail(result02.Error.RenderErrorMessage()); + } + + cs02 = result02.GetValueOrDefault(); + + // Comparing whole tree using IEquatable.Equals(...) + if (!cs01.Equals(cs02)) + { + Console.Error.WriteLine($"Original: {wkt.Wkt}"); + Console.Error.WriteLine($"Parsed 1: {wkt01}"); + Console.Error.WriteLine($"Parsed 2: {cs02}"); + Assert.Fail("Error comparing cs01 and cs02. See beneath..."); + } + + parseCount++; + } + Assert.That(parseCount, Is.GreaterThan(2671), "Not all WKT was parsed"); + } + */ + + + /// <summary> + /// Test parsing of a <see cref="ProjectedCoordinateSystem"/> from WKT + /// </summary> + [Test] + public void TestCoordinateSystem_EPSG_27700_UnitBeforeProjection() + { + Assert.Ignore("Partial match isn't supported yet in new parser!"); + const string wkt = "PROJCS[\"OSGB 1936 / British National Grid\",\n" + + " GEOGCS[\"OSGB 1936\",\n" + + " DATUM[\"OSGB_1936\",\n" + + " SPHEROID[\"Airy 1830\",6377563.396,299.3249646,AUTHORITY[\"EPSG\",\"7001\"]],\n" + + " AUTHORITY[\"EPSG\",\"6277\"]],\n" + + " PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],\n" + + " UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],\n" + + " AUTHORITY[\"EPSG\",\"4277\"]],\n" + + " PROJECTION[\"Transverse_Mercator\"],\n" + + " PARAMETER[\"latitude_of_origin\",49],\n" + + " PARAMETER[\"central_meridian\",-2],\n" + + " PARAMETER[\"scale_factor\",0.9996012717],\n" + + " PARAMETER[\"false_easting\",400000],\n" + + " PARAMETER[\"false_northing\",-100000],\n" + + " UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],\n" + + " AXIS[\"Easting\",EAST],\n" + + " AXIS[\"Northing\",NORTH],\n" + + " AUTHORITY[\"EPSG\",\"27700\"]]"; + + WktProjectedCoordinateSystem pcs = null; + var result01 = builder.Parse(wkt); + Assert.IsTrue(result01.Success, !result01.Success ? result01.Error?.RenderErrorMessage() : "Error!"); + + + + Assert.That(pcs.Name, Is.EqualTo("OSGB 1936 / British National Grid")); + Assert.That(pcs.Authority.Name, Is.EqualTo("EPSG")); + Assert.That(pcs.Authority.Code, Is.EqualTo(27700)); + + var gcs = pcs.GeographicCoordinateSystem; + Assert.That(gcs.Name, Is.EqualTo("OSGB 1936")); + Assert.That(gcs.Authority.Name, Is.EqualTo("EPSG")); + Assert.That(gcs.Authority.Code, Is.EqualTo(4277)); + + //CheckDatum(gcs.HorizontalDatum, "OSGB_1936", "EPSG", 6277); + Assert.That(gcs.HorizontalDatum.Name, Is.EqualTo("OSGB_1936")); + Assert.That(gcs.HorizontalDatum.Authority.Name, Is.EqualTo("EPSG")); + Assert.That(gcs.HorizontalDatum.Authority.Code, Is.EqualTo(6277)); + + //CheckEllipsoid(gcs.HorizontalDatum.Ellipsoid, "Airy 1830", 6377563.396, 299.3249646, "EPSG", 7001); + Assert.That(gcs.HorizontalDatum.Spheroid.Name, Is.EqualTo("Airy 1830")); + Assert.That(gcs.HorizontalDatum.Spheroid.SemiMajorAxis, Is.EqualTo(6377563.396)); + Assert.That(gcs.HorizontalDatum.Spheroid.InverseFlattening, Is.EqualTo(299.3249646)); + Assert.That(gcs.HorizontalDatum.Spheroid.Authority.Name, Is.EqualTo("EPSG")); + Assert.That(gcs.HorizontalDatum.Spheroid.Authority.Code, Is.EqualTo(7001)); + + //CheckPrimem(gcs.PrimeMeridian, "Greenwich", 0, "EPSG", 8901); + Assert.That(gcs.PrimeMeridian.Name, Is.EqualTo("Greenwich")); + Assert.That(gcs.PrimeMeridian.Longitude, Is.EqualTo(0)); + Assert.That(gcs.PrimeMeridian.Authority.Name, Is.EqualTo("EPSG")); + Assert.That(gcs.PrimeMeridian.Authority.Code, Is.EqualTo(8901)); + + //CheckUnit(gcs.AngularUnit, "degree", 0.0174532925199433, "EPSG", 9122); + Assert.That(gcs.AngularUnit.Name, Is.EqualTo("degree")); + Assert.That(gcs.AngularUnit.ConversionFactor, Is.EqualTo(0.0174532925199433)); + Assert.That(gcs.AngularUnit.Authority.Name, Is.EqualTo("EPSG")); + Assert.That(gcs.AngularUnit.Authority.Code, Is.EqualTo(9122)); + + //Assert.AreEqual("Transverse_Mercator", pcs.Projection.ClassName, "Projection Classname"); + Assert.That(pcs.Projection.Name, Is.EqualTo("Transverse_Mercator")); + //Assert.AreEqual("Projection Classname", pcs.Projection.Name); // <= Wkt related? + + /* + CheckProjection(pcs.Projection, "Transverse_Mercator", new[] + { + Tuple.Create("latitude_of_origin", 49d), + Tuple.Create("central_meridian", -2d), + Tuple.Create("scale_factor", 0.9996012717), + Tuple.Create("false_easting", 400000d), + Tuple.Create("false_northing", -100000d) + }); + */ + + //CheckUnit(pcs.LinearUnit, "metre", 1d, "EPSG", 9001); + Assert.That(pcs.Unit.Name, Is.EqualTo("metre")); + Assert.That(pcs.Unit.ConversionFactor, Is.EqualTo(1d)); + Assert.That(pcs.Unit.Authority.Name, Is.EqualTo("EPSG")); + Assert.That(pcs.Unit.Authority.Code, Is.EqualTo(9001)); + + //string newWkt = pcs.WKT.Replace(", ", ","); + //Assert.AreEqual(wkt, newWkt); + } + + + [Test] + public void TestCoordinateSystem_EPSG_28992() + { + // Arrange + string text = @"PROJCS[""Amersfoort / RD New"", GEOGCS[""Amersfoort"", + DATUM[""Amersfoort"",SPHEROID[""Bessel 1841"", 6377397.155, 299.1528128, AUTHORITY[""EPSG"",""7004""]], + TOWGS84[565.2369, 50.0087, 465.658, -0.40685733032239757, -0.3507326765425626, 1.8703473836067956, 4.0812], + AUTHORITY[""EPSG"",""6289""]], + PRIMEM[""Greenwich"", 0.0, AUTHORITY[""EPSG"",""8901""]], + UNIT[""degree"", 0.017453292519943295], + AXIS[""Geodetic latitude"", NORTH], + AXIS[""Geodetic longitude"", EAST], + AUTHORITY[""EPSG"",""4289""]], + PROJECTION[""Oblique_Stereographic"", AUTHORITY[""EPSG"",""9809""]], + PARAMETER[""central_meridian"", 5.387638888888891], + PARAMETER[""latitude_of_origin"", 52.15616055555556], + PARAMETER[""scale_factor"", 0.9999079], + PARAMETER[""false_easting"", 155000.0], + PARAMETER[""false_northing"", 463000.0], + UNIT[""m"", 1.0], + AXIS[""Easting"", EAST], + AXIS[""Northing"", NORTH], + AUTHORITY[""EPSG"",""28992""]]"; + + // Act + var resultCs = builder.Parse(text); + + // Assert + Assert.NotNull(resultCs); + Assert.IsTrue(resultCs.Success);//, resultCs.Error.RenderErrorMessage()); + } + + + [Test] + public void TestParseSrOrg() + { + // Arrange + string text = "PROJCS[\"WGS 84 / Pseudo-Mercator\",GEOGCS[\"Popular Visualisation CRS\"," + + "DATUM[\"Popular_Visualisation_Datum\",SPHEROID[\"Popular Visualisation Sphere\"," + + "6378137,0,AUTHORITY[\"EPSG\",\"7059\"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY[\"EPSG\"," + + "\"6055\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\"," + + "0.01745329251994328,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4055\"]]," + + "PROJECTION[\"Mercator_1SP\"]," + + "PARAMETER[\"central_meridian\",0],PARAMETER[\"scale_factor\",1],PARAMETER[" + + "\"false_easting\",0],PARAMETER[\"false_northing\",0],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],AXIS[\"X\",EAST],AXIS[\"Y\",NORTH]],AUTHORITY[\"EPSG\",\"3785\"]" + ; + // Act + var resultCs = builder.Parse(text); + + // Assert + Assert.NotNull(resultCs); + Assert.IsTrue(resultCs.Success); + } + + + [Test] + public void TestProjNetIssues() + { + // Arrange + string text = "PROJCS[\"International_Terrestrial_Reference_Frame_1992Lambert_Conformal_Conic_2SP\"," + + "GEOGCS[\"GCS_International_Terrestrial_Reference_Frame_1992\"," + + "DATUM[\"International_Terrestrial_Reference_Frame_1992\"," + + "SPHEROID[\"GRS_1980\",6378137,298.257222101]," + + "TOWGS84[0,0,0,0,0,0,0]]," + + "PRIMEM[\"Greenwich\",0]," + + "UNIT[\"Degree\",0.0174532925199433]]," + + "PROJECTION[\"Lambert_Conformal_Conic_2SP\",AUTHORITY[\"EPSG\",\"9802\"]]," + + "PARAMETER[\"Central_Meridian\",-102]," + + "PARAMETER[\"Latitude_Of_Origin\",12]," + + "PARAMETER[\"False_Easting\",2500000]," + + "PARAMETER[\"False_Northing\",0]," + + "PARAMETER[\"Standard_Parallel_1\",17.5]," + + "PARAMETER[\"Standard_Parallel_2\",29.5]," + + "PARAMETER[\"Scale_Factor\",1]," + + "UNIT[\"Meter\",1,AUTHORITY[\"EPSG\",\"9001\"]]]"; + + // Act + var resultCs = builder.Parse(text); + + // Assert + Assert.NotNull(resultCs); + Assert.IsTrue(resultCs.Success); + + + text = "PROJCS[\"Google Maps Global Mercator\",\n" + + "GEOGCS[\"WGS 84\",\n" + + "DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],\n" + + "AUTHORITY[\"EPSG\",\"6326\"]],\n" + + "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],\n" + + "UNIT[\"degree\",0.01745329251994328,AUTHORITY[\"EPSG\",\"9122\"]],\n" + + "AUTHORITY[\"EPSG\",\"4326\"]],\n" + + "PROJECTION[\"Mercator_2SP\"],\n" + + "PARAMETER[\"standard_parallel_1\",0],\n" + + "PARAMETER[\"latitude_of_origin\",0],\n" + + "PARAMETER[\"central_meridian\",0],\n" + + "PARAMETER[\"false_easting\",0],\n" + + "PARAMETER[\"false_northing\",0],\n" + + "UNIT[\"Meter\",1],\n" + + "EXTENSION[\"PROJ4\",\"+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs\"],\n" + + "AUTHORITY[\"EPSG\",\"900913\"]]"; + + // Act + var resultCs2 = builder.Parse(text); + + // Assert + Assert.NotNull(resultCs2); + Assert.IsTrue(resultCs2.Success); + } + + [Test] + public void TestFittedCoordinateSystemWkt() + { + //Assert.Ignore("Is FITTED_CS Support still needed?"); + + string wkt = "FITTED_CS[\"Local coordinate system MNAU (based on Gauss-Krueger)\"," + + "PARAM_MT[\"Affine\"," + + "PARAMETER[\"num_row\",3],PARAMETER[\"num_col\",3],PARAMETER[\"elt_0_0\", 0.883485346527455],PARAMETER[\"elt_0_1\", -0.468458794848877],PARAMETER[\"elt_0_2\", 3455869.17937689],PARAMETER[\"elt_1_0\", 0.468458794848877],PARAMETER[\"elt_1_1\", 0.883485346527455],PARAMETER[\"elt_1_2\", 5478710.88035753],PARAMETER[\"elt_2_2\", 1]]," + + "PROJCS[\"DHDN / Gauss-Kruger zone 3\"," + + "GEOGCS[\"DHDN\"," + + "DATUM[\"Deutsches_Hauptdreiecksnetz\"," + + "SPHEROID[\"Bessel 1841\", 6377397.155, 299.1528128, AUTHORITY[\"EPSG\", \"7004\"]]," + + "TOWGS84[612.4, 77, 440.2, -0.054, 0.057, -2.797, 0.525975255930096]," + + "AUTHORITY[\"EPSG\", \"6314\"]]," + + "PRIMEM[\"Greenwich\", 0, AUTHORITY[\"EPSG\", \"8901\"]]," + + "UNIT[\"degree\", 0.0174532925199433, AUTHORITY[\"EPSG\", \"9122\"]]," + + "AUTHORITY[\"EPSG\", \"4314\"]]," + + "PROJECTION[\"Transverse_Mercator\"]," + + "PARAMETER[\"latitude_of_origin\", 0]," + + "PARAMETER[\"central_meridian\", 9]," + + "PARAMETER[\"scale_factor\", 1]," + + "PARAMETER[\"false_easting\", 3500000]," + + "PARAMETER[\"false_northing\", 0]," + + "UNIT[\"metre\", 1, AUTHORITY[\"EPSG\", \"9001\"]]," + + "AUTHORITY[\"EPSG\", \"31467\"]]" + + "]"; + + + var resultCs = builder.Parse(wkt); + + + // Assert + Assert.NotNull(resultCs); + Assert.That(resultCs.Success, Is.True); + } + + + /* + [Test] + public void TestGeocentricCoordinateSystem() + { + var fac = new CoordinateSystemFactory(); + GeocentricCoordinateSystem fcs = null; + + const string wkt = "GEOCCS[\"TUREF\", " + + "DATUM[\"Turkish_National_Reference_Frame\", " + + "SPHEROID[\"GRS 1980\", 6378137, 298.257222101, AUTHORITY[\"EPSG\", \"7019\"]], " + + "AUTHORITY[\"EPSG\", \"1057\"]], " + + "PRIMEM[\"Greenwich\", 0, AUTHORITY[\"EPSG\", \"8901\"]], " + + "UNIT[\"metre\", 1, AUTHORITY[\"EPSG\", \"9001\"]], " + + "AXIS[\"Geocentric X\", OTHER], AXIS[\"Geocentric Y\", OTHER], AXIS[\"Geocentric Z\", NORTH], " + + "AUTHORITY[\"EPSG\", \"5250\"]]"; + + + using var sr = new StringReader(wkt); + using var reader = new WktToProjTextReader(sr); + + // Act + var resultCs = reader.ReadToEnd(); + + // Assert + Assert.NotNull(resultCs); + Assert.IsTrue(resultCs.Success); + + + // Convert to Proj + var converter = new WktToProjConverter(fac); + var projCs = converter.Convert(resultCs.Value) as GeocentricCoordinateSystem; + + Assert.NotNull(projCs); + Assert.AreEqual("TUREF", projCs.Name); + Assert.AreEqual("EPSG", projCs.Authority); + Assert.AreEqual(5250, projCs.AuthorityCode); + + Assert.AreEqual("Turkish_National_Reference_Frame", projCs.HorizontalDatum.Name); + Assert.AreEqual("EPSG", projCs.HorizontalDatum.Authority); + Assert.AreEqual(1057, projCs.HorizontalDatum.AuthorityCode); + + } +*/ + + + [Test] + public void ParseWktCreatedByCoordinateSystem() + { + Assert.Ignore("CreateHorizontalDatum contains a check on String.IsNullOrWhitespace for the name and throws....????"); + // Sample WKT from an external source. + string sampleWkt = + "PROJCS[\"\", " + + "GEOGCS[\"\", " + + "DATUM[\"\", " + + "SPHEROID[\"GRS_1980\", 6378137, 298.2572221010042] " + + "], " + + "PRIMEM[\"Greenwich\", 0], " + + "UNIT[\"Degree\", 0.017453292519943295]" + + "], " + + "PROJECTION[\"Transverse_Mercator\"], " + + "PARAMETER[\"False_Easting\", 500000], " + + "PARAMETER[\"False_Northing\", 0], " + + "PARAMETER[\"Central_Meridian\", -75], " + + "PARAMETER[\"Scale_Factor\", 0.9996], " + + "UNIT[\"Meter\", 1]" + + "]"; + + // Act + var resultCs = builder.Parse(sampleWkt); + + // Assert + Assert.NotNull(resultCs); + Assert.IsTrue(resultCs.Success); + } + + [Test] + public void TestAxisPresent() + { + string parseText = "PROJCS[\"CH1903+ / LV95\",\n" + + " GEOGCS[\"CH1903+\",\n" + + " DATUM[\"CH1903+\",\n" + + " SPHEROID[\"Bessel 1841\",6377397.155,299.1528128,AUTHORITY[\"EPSG\",\"7004\"]],\n" + + " TOWGS84[674.374,15.056,405.346,0,0,0,0],AUTHORITY[\"EPSG\",\"6150\"]],\n" + + " PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],\n" + + " UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],\n" + + " AUTHORITY[\"EPSG\",\"4150\"]],\n" + + " PROJECTION[\"Hotine_Oblique_Mercator_Azimuth_Center\"],\n" + + " PARAMETER[\"latitude_of_center\",46.95240555555556],\n" + + " PARAMETER[\"longitude_of_center\",7.439583333333333],\n" + + " PARAMETER[\"azimuth\",90],\n" + + " PARAMETER[\"rectified_grid_angle\",90],\n" + + " PARAMETER[\"scale_factor\",1],\n" + + " PARAMETER[\"false_easting\",2600000],\n" + + " PARAMETER[\"false_northing\",1200000],\n" + + " UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],\n" + + " AXIS[\"Easting\",EAST],\n" + + " AXIS[\"Northing\",NORTH],\n" + + " AUTHORITY[\"EPSG\",\"2056\"]]"; + + // Act + var resultCs = builder.Parse(parseText); + + // Assert + Assert.NotNull(resultCs); + Assert.IsTrue(resultCs.Success); + } + + + readonly private CoordinateSystemFactory _coordinateSystemFactory = new CoordinateSystemFactory(); + + /// <summary> + /// This test reads in a file with 2671 pre-defined coordinate systems and projections, + /// and tries to parse them. + /// </summary> + [Test] + public void ParseAllWKTs_OldParser() + { + int parseCount = 0; + foreach (var wkt in SRIDReader.GetSrids()) + { + var cs1 = _coordinateSystemFactory.CreateFromWkt(wkt.Wkt); + Assert.IsNotNull(cs1, "Could not parse WKT: " + wkt); + var cs2 = _coordinateSystemFactory.CreateFromWkt(wkt.Wkt.Replace("[", "(").Replace("]", ")")); + Assert.That(cs1.EqualParams(cs2), Is.True); + parseCount++; + } + Assert.That(parseCount, Is.GreaterThan(2671), "Not all WKT was parsed"); + } + + + private readonly WktToProjBuilder builder = new WktToProjBuilder(); + + private readonly WktParser.Context ctx = new WktParser.Context {builder = new WktToProjBuilder()}; + + [Test] + public void ParseAllWKTs_PidginParser() + { + int parseCount = 0; + foreach (var wkt in SRIDReader.GetSrids()) + { + //var cs1 = _coordinateSystemFactory.CreateFromWkt(wkt.Wkt); + //using var sr = new StringReader(wkt.Wkt); + var presult = WktParser.SpatialReferenceSystemParser(ctx).Parse(wkt.Wkt); + Assert.That(presult.Success, Is.True); + var cs1 = (CoordinateSystem) presult.Value; + Assert.IsNotNull(cs1, "Could not parse WKT: " + wkt); + + //var presult2 = builder.Parse(wkt.Wkt.Replace("[", "(").Replace("]", ")")); + var presult2 = WktParser.SpatialReferenceSystemParser(ctx).Parse(wkt.Wkt.Replace("[", "(").Replace("]", ")")); + Assert.That(presult2.Success, Is.True); + var cs2 = (CoordinateSystem) presult2.Value; + + Assert.That(cs1.EqualParams(cs2), Is.True); + parseCount++; + } + Assert.That(parseCount, Is.GreaterThan(2671), "Not all WKT was parsed"); + } +} diff --git a/test/ProjNet.Tests/WKT/WktToProjConverterTests.cs b/test/ProjNet.Tests/WKT/WktToProjConverterTests.cs new file mode 100644 index 0000000..4e89f8f --- /dev/null +++ b/test/ProjNet.Tests/WKT/WktToProjConverterTests.cs @@ -0,0 +1,115 @@ +using System.IO; +using NUnit.Framework; +using ProjNet.CoordinateSystems; +using ProjNet.IO.Wkt.Core; +using ProjNet.IO.Wkt.Tree; +using ProjNet.Wkt; + +namespace ProjNET.Tests.WKT; + +public class WktToProjConverterTests +{ + [Test] + public void TestConvert_EPGS_28992() + { + // Arrange + string text = @"PROJCS[""Amersfoort / RD New"", GEOGCS[""Amersfoort"", + DATUM[""Amersfoort"",SPHEROID[""Bessel 1841"", 6377397.155, 299.1528128, AUTHORITY[""EPSG"",""7004""]], + TOWGS84[565.2369, 50.0087, 465.658, -0.40685733032239757, -0.3507326765425626, 1.8703473836067956, 4.0812], + AUTHORITY[""EPSG"",""6289""]], + PRIMEM[""Greenwich"", 0.0, AUTHORITY[""EPSG"",""8901""]], + UNIT[""degree"", 0.017453292519943295], + AXIS[""Geodetic latitude"", NORTH], + AXIS[""Geodetic longitude"", EAST], + AUTHORITY[""EPSG"",""4289""]], + PROJECTION[""Oblique_Stereographic"", AUTHORITY[""EPSG"",""9809""]], + PARAMETER[""central_meridian"", 5.387638888888891], + PARAMETER[""latitude_of_origin"", 52.15616055555556], + PARAMETER[""scale_factor"", 0.9999079], + PARAMETER[""false_easting"", 155000.0], + PARAMETER[""false_northing"", 463000.0], + UNIT[""m"", 1.0], + AXIS[""Easting"", EAST], + AXIS[""Northing"", NORTH], + AUTHORITY[""EPSG"",""28992""]]"; + using var sr = new StringReader(text); + using var reader = new WktTextReader(sr); + + var result = reader.ReadToEnd(); + + Assert.NotNull(result); + Assert.IsTrue(result.Success);//, resultCs.Error.RenderErrorMessage()); + + var wktCs = result.GetValueOrDefault(); + + var converter = new WktToProjConverter(); + + // Act + var projCs = converter.Convert(wktCs); + + // Assert + Assert.NotNull(projCs); + Assert.AreEqual(wktCs.Name, projCs.Name); + + Assert.IsInstanceOf<WktProjectedCoordinateSystem>(wktCs); + Assert.IsInstanceOf<ProjectedCoordinateSystem>(projCs); + + var projectedCs = projCs as ProjectedCoordinateSystem; + + Assert.AreEqual("Oblique_Stereographic", projectedCs.Projection.Name); + Assert.AreEqual("EPSG", projectedCs.Projection.Authority); + Assert.AreEqual(9809, projectedCs.Projection.AuthorityCode); + Assert.AreEqual(5, projectedCs.Projection.NumParameters); + + var first = projectedCs.Projection.GetParameter(0); + Assert.AreEqual("central_meridian", first.Name); + Assert.AreEqual(5.387638888888891, first.Value); + + var last = projectedCs.Projection.GetParameter(4); + Assert.AreEqual("false_northing", last.Name); + Assert.AreEqual(463000.0, last.Value); + + Assert.AreEqual("EPSG", projectedCs.Authority); + Assert.AreEqual(28992, projectedCs.AuthorityCode); + + var firstAxis = projectedCs.AxisInfo[0]; + Assert.AreEqual("Easting", firstAxis.Name); + Assert.AreEqual(AxisOrientationEnum.East, firstAxis.Orientation); + } + + + [Test] + public void ParseAllWKTs() + { + int parseCount = 0; + foreach (var wkt in SRIDReader.GetSrids()) + { + using var sr01 = new StringReader(wkt.Wkt); + using var wktReader01 = new WktTextReader(sr01); + var result01 = wktReader01.ReadToEnd(); + Assert.That(result01.Success, Is.True); + var cs01 = result01.Value; + Assert.IsNotNull(cs01, "Could not parse WKT: " + wkt.Wkt); + var converter01 = new WktToProjConverter(); + var projObj01 = converter01.Convert(cs01); + + //@TODO: Create outputWriter and formater for changing delimiters in right context: .Replace("[", "(").Replace("]", ")"))); + using var sr02 = new StringReader(wkt.Wkt); + using var wktReader02 = new WktTextReader(sr02); + var result02 = wktReader02.ReadToEnd(); + Assert.That(result02.Success, Is.True); + var cs02 = result02.Value; + var converter02 = new WktToProjConverter(); + var projObj02 = converter01.Convert(cs02); + + // Comparing whole tree using IEquatable.Equals(...) + Assert.That(cs01.Equals(cs02), Is.True); + + // EqualParam fails for now and I don't dare to fix it yet. + //Assert.That(projObj01.EqualParams(projObj02), Is.True); + + parseCount++; + } + Assert.That(parseCount, Is.GreaterThan(2671), "Not all WKT was parsed"); + } +}