From 9157e532257ba493e75148b54581887c0204f3d9 Mon Sep 17 00:00:00 2001 From: Henry Roeland Date: Sun, 14 Jul 2024 12:51:19 +0200 Subject: [PATCH 1/7] [WIP|Draft|Spike] First try using Pidgin Parser lib for WKT1Parser. --- src/ProjNet/ProjNET.csproj | 4 + src/ProjNet/Wkt/Utils.cs | 26 + src/ProjNet/Wkt/Wkt1Parser.cs | 573 ++++++++++++++++++ .../Wkt/v1/12-063r5_CRS_Well-known_Text.pdf | Bin 0 -> 1748455 bytes src/ProjNet/Wkt/v1/DateTimeBuilder.cs | 119 ++++ src/ProjNet/Wkt/v1/tree/AreaDescription.cs | 34 ++ src/ProjNet/Wkt/v1/tree/AuthorityCitation.cs | 28 + src/ProjNet/Wkt/v1/tree/BBox.cs | 56 ++ src/ProjNet/Wkt/v1/tree/Extent.cs | 8 + src/ProjNet/Wkt/v1/tree/IWktAttribute.cs | 10 + src/ProjNet/Wkt/v1/tree/Identifier.cs | 89 +++ src/ProjNet/Wkt/v1/tree/Scope.cs | 34 ++ src/ProjNet/Wkt/v1/tree/TemporalExtent.cs | 74 +++ src/ProjNet/Wkt/v1/tree/Uri.cs | 45 ++ src/ProjNet/Wkt/v1/tree/VerticalExtent.cs | 50 ++ test/ProjNet.Tests/ProjNET.Tests.csproj | 4 + test/ProjNet.Tests/WKT/Wkt1ParserTests.cs | 437 +++++++++++++ 17 files changed, 1591 insertions(+) create mode 100644 src/ProjNet/Wkt/Utils.cs create mode 100644 src/ProjNet/Wkt/Wkt1Parser.cs create mode 100644 src/ProjNet/Wkt/v1/12-063r5_CRS_Well-known_Text.pdf create mode 100644 src/ProjNet/Wkt/v1/DateTimeBuilder.cs create mode 100644 src/ProjNet/Wkt/v1/tree/AreaDescription.cs create mode 100644 src/ProjNet/Wkt/v1/tree/AuthorityCitation.cs create mode 100644 src/ProjNet/Wkt/v1/tree/BBox.cs create mode 100644 src/ProjNet/Wkt/v1/tree/Extent.cs create mode 100644 src/ProjNet/Wkt/v1/tree/IWktAttribute.cs create mode 100644 src/ProjNet/Wkt/v1/tree/Identifier.cs create mode 100644 src/ProjNet/Wkt/v1/tree/Scope.cs create mode 100644 src/ProjNet/Wkt/v1/tree/TemporalExtent.cs create mode 100644 src/ProjNet/Wkt/v1/tree/Uri.cs create mode 100644 src/ProjNet/Wkt/v1/tree/VerticalExtent.cs create mode 100644 test/ProjNet.Tests/WKT/Wkt1ParserTests.cs diff --git a/src/ProjNet/ProjNET.csproj b/src/ProjNet/ProjNET.csproj index e5cfcda..5a5b09c 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 + + + + diff --git a/src/ProjNet/Wkt/Utils.cs b/src/ProjNet/Wkt/Utils.cs new file mode 100644 index 0000000..49c442e --- /dev/null +++ b/src/ProjNet/Wkt/Utils.cs @@ -0,0 +1,26 @@ +using System; + +namespace ProjNet.Wkt +{ + /// + /// Helper functions for WKT Parser(s). + /// + public class Utils + { + internal static double CalcAsFractionOf(uint i, uint f) + { + // Convert f to string to count the digits + string fstr = f.ToString(); + int fractionDigits = fstr.Length; + + double d = i; + + // 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/Wkt/Wkt1Parser.cs b/src/ProjNet/Wkt/Wkt1Parser.cs new file mode 100644 index 0000000..ed32ff8 --- /dev/null +++ b/src/ProjNet/Wkt/Wkt1Parser.cs @@ -0,0 +1,573 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Security; +using Pidgin; +using ProjNet.IO.CoordinateSystems; +using ProjNet.Wkt.v1; +using ProjNet.Wkt.v1.tree; +using static Pidgin.Parser; +using Uri = ProjNet.Wkt.v1.tree.Uri; + +namespace ProjNet.Wkt +{ + /// + /// WKT1Parser - Base parser for WKT 1 using Parser Combinator Library Pidgin. + /// + /// + public class Wkt1Parser + { + + // 6.3.1 Basic characters + + // + internal static readonly Parser 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') + ); + + // + internal static readonly Parser 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') + ); + + // + internal static readonly Parser Didgit = OneOf( + Char('0'), Char('1'), Char('2'), + Char('3'), Char('4'), Char('5'), + Char('6'), Char('7'), Char('8'), + Char('9') + ); + + // + internal static readonly Parser Space = Char(' '); + + // + internal static readonly Parser DoubleQuote = Char('"'); + + // + internal static readonly Parser NumberSign = Char('#'); + + // + internal static readonly Parser Percent = Char('%'); + + // + internal static readonly Parser Ampersand = Char('&'); + + // + internal static readonly Parser Quote = Char('\''); + + // + internal static readonly Parser LeftParen = Char('('); + // + internal static readonly Parser RightParen = Char(')'); + + // + internal static readonly Parser Asterisk = Char('*'); + + // + internal static readonly Parser PlusSign = Char('+'); + + // + internal static readonly Parser Comma = Char(','); + + // ::- + internal static readonly Parser MinusSign = Char('-'); + internal static readonly Parser Hyphen = Char('-'); + + // + internal static readonly Parser Period = Char('.'); + + // + internal static readonly Parser Solidus = Char('/'); + // + internal static readonly Parser ReverseSolidus = Char('\\'); + + // + internal static readonly Parser Colon = Char(':'); + // + internal static readonly Parser SemiColon = Char(';'); + + // + internal static readonly Parser LessThanOperator = Char('<'); + // + internal static readonly Parser EqualsOperator = Char('='); + // + internal static readonly Parser GreaterThanOperator = Char('>'); + + // + internal static readonly Parser QuestionMark = Char('?'); + + // + internal static readonly Parser LeftBracket = Char('['); + // + internal static readonly Parser RightBracket = Char(']'); + + // + internal static readonly Parser Circumflex = Char('^'); + + // + internal static readonly Parser Underscore = Char('_'); + + // + internal static readonly Parser LeftBrace = Char('{'); + // + internal static readonly Parser RightBrace = Char('}'); + + // + internal static readonly Parser VerticalBar = Char('|'); + + // + internal static readonly Parser DegreeSymbol = Char('\u00B0'); + + + // 6.3.2 Numbers + + // + internal static readonly Parser Sign = OneOf(PlusSign, MinusSign); + // + internal static readonly Parser UnsignedIntegerString = + Wkt1Parser.Didgit.AtLeastOnceString(); + internal static readonly Parser UnsignedInteger = + UnsignedIntegerString + .Select(uint.Parse); + + // + internal static readonly Parser ExactNumericLiteralDotted = + Period + .Then(UnsignedInteger, (c, ui) => ui / Math.Pow(10, Math.Floor(Math.Log10(ui) + 1))); + + internal static readonly Parser ExactNumericLiteral = + UnsignedInteger.Optional() + .Then(ExactNumericLiteralDotted.Optional(), (ui, d) => ui.GetValueOrDefault() + d.GetValueOrDefault()); + + + // + internal static readonly Parser SignedInteger = + Sign.Optional() + .Then(UnsignedInteger, (c, ui) => (int) ((c.HasValue && c.Value == '-' ? -1 : 1) * ui)); + + // + internal static readonly Parser Exponent = + SignedInteger; + + // + internal static readonly Parser Mantissa = + ExactNumericLiteral; + + // + internal static readonly Parser ApproximateNumericLiteralExp = + OneOf(Char('e'),Char('E')) + .Then(Exponent) + .Select(e => Math.Pow(10, e)); + internal static readonly Parser ApproximateNumericLiteral = + Mantissa.Then(ApproximateNumericLiteralExp.Optional(), (m, e) => m * (e.HasValue ? e.Value : 1)); + + // + internal static readonly Parser UnsignedNumericLiteral = OneOf( + ApproximateNumericLiteral, + ExactNumericLiteral + ); + + // + internal static readonly Parser SignedNumericLiteral = + Sign.Optional() + .Then(UnsignedNumericLiteral, (s, d) => (double) ((s.HasValue && s.Value == '-' ? -1 : 1) * d)); + + + // + internal static readonly Parser Number = OneOf( + SignedNumericLiteral, + UnsignedNumericLiteral) + .Labelled("number"); + + + // 6.3.3 Date and time + + // ::= !! two digits + internal static readonly Parser Day = Wkt1Parser.Didgit + .Repeat(2) + .Select(d => uint.Parse(new string(d.ToArray()))); + // ::= !! two digits + internal static readonly Parser Month = Wkt1Parser.Didgit + .Repeat(2) + .Select(m => uint.Parse(new string(m.ToArray()))); + // ::= !! four digits + internal static readonly Parser Year = Wkt1Parser.Didgit + .Repeat(4) + .Select(y => uint.Parse(new string(y.ToArray()))); + + // ::= + // !! two digits including leading zero if less than 10 + internal static readonly Parser Hour = Wkt1Parser.Didgit + .Repeat(2) + .Select(h => uint.Parse(new string(h.ToArray()))); + + // ::= + // !! two digits including leading zero if less than 10 + internal static readonly Parser Minute = Wkt1Parser.Didgit + .Repeat(2) + .Select(h => uint.Parse(new string(h.ToArray()))); + + // ::= + // !! two digits including leading zero if less than 10 + internal static readonly Parser SecondsInteger = Wkt1Parser.Didgit + .Repeat(2) + .Select(h => uint.Parse(new string(h.ToArray()))); + // ::= + internal static readonly Parser SecondsFraction = UnsignedInteger; + + // ::= [ [ ] ] + // !! 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 SecondsDotted = Period.Then(SecondsFraction); + + internal static readonly Parser Second = SecondsInteger.Then(SecondsDotted.Optional(), + (u, mf) => new DateTimeBuilder() + .SetSeconds((int) u) + .SetMilliseconds((int) mf.GetValueOrDefault())); + + // ::= Z + internal static readonly Parser UtcDesignator = Char('Z'); + + // ::= { | } [ ] + internal static readonly Parser ColonMinute = Colon.Then(Minute); + internal static readonly Parser LocalTimeZoneDesignator = Sign + .Then(Hour, (ms, u) => (ms == '-' ? -1 : 1) * u) + .Then(ColonMinute.Optional(), (h, mm) => new TimeSpan(0, (int) h, (int) mm.GetValueOrDefault(), 0)); + + //