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");
+    }
+}