From e99c39ec5ff2886ddb98f139b89a65a42d5764cf Mon Sep 17 00:00:00 2001 From: Henry Roeland Date: Tue, 30 Jul 2024 20:25:46 +0200 Subject: [PATCH 1/2] PoC getting Antlr4 to work as WktCrs parser. FittedCS still Todo. --- src/ProjNet/IO/Wkt/WktCrs.g4 | 270 ++++++++++ src/ProjNet/IO/Wkt/WktToProjBuilder.cs | 474 ++++++++++++++++++ src/ProjNet/ProjNET.csproj | 14 +- .../WKT/WktToProjBuilderTests.cs | 105 ++++ 4 files changed, 862 insertions(+), 1 deletion(-) create mode 100644 src/ProjNet/IO/Wkt/WktCrs.g4 create mode 100644 src/ProjNet/IO/Wkt/WktToProjBuilder.cs create mode 100644 test/ProjNet.Tests/WKT/WktToProjBuilderTests.cs diff --git a/src/ProjNet/IO/Wkt/WktCrs.g4 b/src/ProjNet/IO/Wkt/WktCrs.g4 new file mode 100644 index 0000000..aad1ba1 --- /dev/null +++ b/src/ProjNet/IO/Wkt/WktCrs.g4 @@ -0,0 +1,270 @@ +/* + [The "BSD licence"] Copyright (c) 2023 Nikolay Fiykov All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * For parsing propeties file (like GeoTools epsg.properties), use starting rule "propsFile". For parsing single WKT CRS definition, use starting rule "wkt". + */ + +// $antlr-format alignTrailingComments true, columnLimit 150, minEmptyLines 1, maxEmptyLinesToKeep 1, reflowComments false, useTab false +// $antlr-format allowShortRulesOnASingleLine false, allowShortBlocksOnASingleLine true, alignSemicolons hanging, alignColons hanging + +grammar WktCrs; + + +propsFile + : propRow* EOF + ; + +propRow + : commentLine + | epsgDefLine + ; + +commentLine + : COMMENT_LINE + ; + +epsgDefLine + : epsgCode EQ wkt + ; + +wkt + : compdcs + | projcs + | geogcs + | vertcs + | geoccs + | localcs + ; + +compdcs + : 'COMPD_CS' LPAR name COMMA (projcs | geogcs) COMMA vertcs COMMA authority RPAR + ; + +projcs + : 'PROJCS' LPAR name COMMA geogcs COMMA projection COMMA (parameter COMMA)+ unit COMMA ( + extension COMMA + )? ( + axis COMMA + )* authority RPAR + ; + +geoccs + : 'GEOCCS' LPAR name COMMA datum COMMA primem COMMA unit COMMA (axis COMMA)+ authority RPAR + ; + +geogcs + : 'GEOGCS' LPAR name COMMA datum COMMA primem COMMA unit (COMMA axis)* (COMMA authority)? RPAR + ; + +vertcs + : 'VERT_CS' LPAR name COMMA vertdatum COMMA unit COMMA axis COMMA authority RPAR + ; + +localcs + : 'LOCAL_CS' LPAR name COMMA localdatum COMMA unit COMMA (axis COMMA)+ authority RPAR + ; + +datum + : 'DATUM' LPAR name COMMA spheroid (COMMA towgs84)? (COMMA authority)? RPAR + ; + +vertdatum + : 'VERT_DATUM' LPAR name COMMA type COMMA authority RPAR + ; + +localdatum + : 'LOCAL_DATUM' LPAR name COMMA type (COMMA authority)? RPAR + ; + +spheroid + : 'SPHEROID' LPAR name COMMA semiMajorAxis COMMA inverseFlattening (COMMA authority)? RPAR + ; + +towgs84 + : 'TOWGS84' LPAR dx COMMA dy COMMA dz (COMMA ex COMMA ey COMMA ez (COMMA ppm)?)? RPAR + ; + +extension + : 'EXTENSION' LPAR name COMMA projtext RPAR {} + ; + +authority + : 'AUTHORITY' LPAR authorityName COMMA code RPAR + ; + +primem + : 'PRIMEM' LPAR name COMMA longitude (COMMA unit)? (COMMA authority)? RPAR + ; + +unit + : 'UNIT' LPAR name COMMA conversionFactor (COMMA authority)? RPAR + ; + +axis + : 'AXIS' LPAR name COMMA axisOrient RPAR + ; + +projection + : 'PROJECTION' LPAR name (COMMA authority)? RPAR + ; + +parameter + : 'PARAMETER' LPAR name COMMA value RPAR + ; + +authorityName + : '"EPSG"' + | '"ESRI"' + ; + +axisOrient + : 'EAST' + | 'WEST' + | 'NORTH' + | 'SOUTH' + | 'NORTH_EAST' + | 'NORTH_WEST' + | 'UP' + | 'DOWN' + | 'OTHER' + | 'GEOCENTRIC_X' + | 'GEOCENTRIC_Y' + | 'GEOCENTRIC_Z' + | name + ; + +epsgCode + : PKEY + | NUMBER + ; + +name + : TEXT + ; + +number + : NUMBER + ; + +type + : NUMBER + ; + +semiMajorAxis + : NUMBER + ; + +inverseFlattening + : NUMBER + ; + +dx + : NUMBER + ; + +dy + : NUMBER + ; + +dz + : NUMBER + ; + +ex + : NUMBER + ; + +ey + : NUMBER + ; + +ez + : NUMBER + ; + +ppm + : NUMBER + ; + +projtext + : TEXT + ; + +code + : TEXT + | NUMBER + ; + +longitude + : NUMBER + ; + +conversionFactor + : NUMBER + ; + +value + : NUMBER + ; + +NUMBER + : PM? INT ('.' INT)? EXP? + ; + +TEXT + : '"' ('""' | ~'"')* '"' + ; + +PKEY + : [A-Z] [0-9A-Z]+ + ; + +COMMENT_LINE + : '#' ~[\r\n]* + ; + +WS + : [ \r\n\t]+ -> skip + ; + +COMMA + : ',' + ; + +LPAR + : '[' + | '(' + ; + +RPAR + : ']' + | ')' + ; + +EQ + : '=' + ; + +fragment INT + : [0-9]+ + ; + +fragment EXP + : [Ee] PM? INT + ; + +fragment PM + : '+' + | '-' + ; \ No newline at end of file diff --git a/src/ProjNet/IO/Wkt/WktToProjBuilder.cs b/src/ProjNet/IO/Wkt/WktToProjBuilder.cs new file mode 100644 index 0000000..f80244e --- /dev/null +++ b/src/ProjNet/IO/Wkt/WktToProjBuilder.cs @@ -0,0 +1,474 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Antlr4.Runtime; +using Antlr4.Runtime.Misc; +using Antlr4.Runtime.Tree; +using ProjNet.CoordinateSystems; + +namespace ProjNet.IO.Wkt +{ + public class WktToProjBuilder : WktCrsBaseVisitor + { + private readonly CoordinateSystemFactory factory; + + public WktToProjBuilder() + { + factory = new CoordinateSystemFactory(); + } + + + public class Authority + { + public string Name { get; set; } + + public int Code { get; set; } + } + + + public override object VisitName(WktCrsParser.NameContext context) + { + return context.GetText().Trim(new char[] {'\"', ' '}); + } + + public override object VisitValue(WktCrsParser.ValueContext context) + { + string valueStr = context.GetText(); + if (double.TryParse(valueStr, NumberStyles.Any, CultureInfo.InvariantCulture, out double d)) + return d; + + return double.NaN; + } + + public override object VisitLongitude(WktCrsParser.LongitudeContext context) + { + string valueStr = context.GetText(); + if (double.TryParse(valueStr, NumberStyles.Any, CultureInfo.InvariantCulture, out double d)) + return d; + + return double.NaN; + } + + public override object VisitAuthorityName(WktCrsParser.AuthorityNameContext context) + { + return context.GetText().Trim(new char[] {'\"', ' '}); + } + + public override object VisitCode(WktCrsParser.CodeContext context) + { + if (!context.IsEmpty) + { + string codeStr = context.GetText(); + codeStr = codeStr.Trim(new char[] {' ', '\"'}); + if (codeStr.Contains("_")) + { + codeStr = codeStr.Substring(0, codeStr.IndexOf('_')); + } + + if (!string.IsNullOrWhiteSpace(codeStr) && int.TryParse(codeStr, NumberStyles.Any, CultureInfo.InvariantCulture, out int nmbr)) + { + return nmbr; + } + } + + return -1; + } + + public override object VisitAxisOrient(WktCrsParser.AxisOrientContext context) + { + + if (!context.IsEmpty) + { + string direction = context.GetText().Trim(new char[]{' ', '\"'}); + if (Enum.TryParse(direction, true, out AxisOrientationEnum enumVal)) + { + return enumVal; + } + } + + return AxisOrientationEnum.Other; + } + + public override object VisitAuthority([NotNull] WktCrsParser.AuthorityContext context) + { + string authName = string.Empty; + if (context.authorityName() != null) + authName = (string)Visit(context.authorityName()); + + int code = -1; + if (context.code() != null) + code = (int) Visit(context.code()); + + return new Authority {Name = authName, Code = code}; + } + + public override object VisitAxis(WktCrsParser.AxisContext context) + { + string name = string.Empty; + if (context.name()!=null) + name = (string) Visit(context.name()); + + var orient = AxisOrientationEnum.Other; + if (context.axisOrient()!=null) + orient = (AxisOrientationEnum) Visit(context.axisOrient()); + + return new AxisInfo(name, orient); + } + + public override object VisitExtension([NotNull] WktCrsParser.ExtensionContext context) + { + string name = string.Empty; + if (context.name() != null) + name = (string) Visit(context.name()); + + string projText = string.Empty; + if (context.projtext() != null) + projText = (string) Visit(context.projtext()); + + // No ProjNet object for Extension so returning a tuple. + return (name, projText); + } + + public override object VisitTowgs84(WktCrsParser.Towgs84Context context) + { + double dx = 0.0d; + if (!context.dx().IsEmpty && double.TryParse(context.dx().GetText(), NumberStyles.Any, CultureInfo.InvariantCulture, out double dxResult )) + dx = dxResult; + + double dy = 0.0d; + if (!context.dy().IsEmpty && double.TryParse(context.dy().GetText(), NumberStyles.Any, CultureInfo.InvariantCulture, out double dyResult )) + dy = dyResult; + + double dz = 0.0d; + if (!context.dz().IsEmpty && double.TryParse(context.dz().GetText(), NumberStyles.Any, CultureInfo.InvariantCulture, out double dzResult )) + dz = dzResult; + + double ex = 0.0d; + if (!context.ex().IsEmpty && double.TryParse(context.ex().GetText(), NumberStyles.Any, CultureInfo.InvariantCulture, out double exResult )) + ex = exResult; + + double ey = 0.0d; + if (!context.ey().IsEmpty && double.TryParse(context.ey().GetText(), NumberStyles.Any, CultureInfo.InvariantCulture, out double eyResult )) + ey = eyResult; + + double ez = 0.0d; + if (!context.ez().IsEmpty && double.TryParse(context.ez().GetText(), NumberStyles.Any, CultureInfo.InvariantCulture, out double ezResult )) + ez = ezResult; + + double ppm = 0.0d; + if (!context.ppm().IsEmpty && double.TryParse(context.ppm().GetText(), NumberStyles.Any, CultureInfo.InvariantCulture, out double ppmResult )) + ppm = ppmResult; + + return new Wgs84ConversionInfo(dx, dy, dz, ex, ey, ez, ppm); + } + + public override object VisitProjection(WktCrsParser.ProjectionContext context) + { + string name = string.Empty; + if (context.name() != null) + name = (string) Visit(context.name()); + + Authority authority = null; + if (context.authority() != null) + authority = (Authority) Visit(context.authority()); + + return new Projection(name, new List(), name, authority?.Name, authority!=null ? authority.Code:-1, string.Empty, string.Empty, string.Empty); + } + + public override object VisitParameter(WktCrsParser.ParameterContext context) + { + string name = string.Empty; + if (context.name() != null) + name = (string) Visit(context.name()); + + double value = double.NaN; + if (context.value() != null) + value = (double) Visit(context.value()); + + return new ProjectionParameter(name, value); + } + + + public override object VisitSpheroid(WktCrsParser.SpheroidContext context) + { + string name = string.Empty; + if (context.name() != null) + name = (string) Visit(context.name()); + + double semiMajorAxis = double.NaN; + if (context.semiMajorAxis() != null) + semiMajorAxis = (double)Visit(context.semiMajorAxis()); + + double inverseFlattening = double.NaN; + if (context.inverseFlattening() != null) + inverseFlattening = (double)Visit(context.inverseFlattening()); + + Authority authority = null; + if (context.authority() != null) + authority = (Authority) Visit(context.authority()); + + return new Ellipsoid(semiMajorAxis, 0.0, inverseFlattening, true, LinearUnit.Metre, name, + authority?.Name, authority!=null?authority.Code:-1, string.Empty, string.Empty, string.Empty); + + } + + public override object VisitSemiMajorAxis(WktCrsParser.SemiMajorAxisContext context) + { + if (!context.IsEmpty && double.TryParse(context.GetText(), NumberStyles.Any, CultureInfo.InvariantCulture, out double d)) + return d; + return double.NaN; + } + + public override object VisitInverseFlattening(WktCrsParser.InverseFlatteningContext context) + { + if (!context.IsEmpty && double.TryParse(context.GetText(), NumberStyles.Any, CultureInfo.InvariantCulture, out double d)) + return d; + return double.NaN; + } + + public override object VisitConversionFactor(WktCrsParser.ConversionFactorContext context) + { + if (!context.IsEmpty && double.TryParse(context.GetText(), NumberStyles.Any, CultureInfo.InvariantCulture, out double d)) + return d; + return double.NaN; + } + + + public override object VisitDatum(WktCrsParser.DatumContext context) + { + string name = string.Empty; + if (context.name() != null) + name = (string) Visit(context.name()); + + Ellipsoid spheroid = null; + if (context.spheroid() != null) + spheroid = (Ellipsoid) Visit(context.spheroid()); + + Wgs84ConversionInfo wgs84 = null; + if (context.towgs84() != null) + wgs84 = (Wgs84ConversionInfo) Visit(context.towgs84()); + + Authority authority = null; + if (context.authority() != null) + { + authority = (Authority) Visit(context.authority()); + } + + var result = this.factory.CreateHorizontalDatum( + name, DatumType.HD_Geocentric, ellipsoid: spheroid, toWgs84: wgs84); + + if (authority != null) + { + result.Authority = authority.Name; + result.AuthorityCode = authority.Code; + } + + return result; + } + + public override object VisitUnit(WktCrsParser.UnitContext context) + { + string name = string.Empty; + if (context.name() != null) + name = (string) Visit(context.name()); + + double factor = double.NaN; + if (context.conversionFactor() != null) + factor = (double) Visit(context.conversionFactor()); + + Authority authority = null; + if (context.authority() != null) + authority = (Authority) Visit(context.authority()); + + + return new Unit(factor,name, authority?.Name, authority!=null?authority.Code:-1 , string.Empty, string.Empty, string.Empty); + } + + public override object VisitPrimem(WktCrsParser.PrimemContext context) + { + string name = string.Empty; + if (context.name() != null) + name = (string) Visit(context.name()); + + double longitude = double.NaN; + if (context.longitude() != null) + longitude = (double) Visit(context.longitude()); + + AngularUnit au = null; + if (context.unit() != null) + { + var unit = (Unit) Visit(context.unit()); + if (unit != null && unit.Name.Equals("degree")) + { + au = new AngularUnit(unit.ConversionFactor, unit.Name, unit.Authority, unit.AuthorityCode, + string.Empty, string.Empty, string.Empty); + } + } + + + Authority authority = null; + if (context.authority() != null) + { + authority = (Authority) Visit(context.authority()); + } + + 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 override object VisitGeogcs(WktCrsParser.GeogcsContext context) + { + string name = string.Empty; + if (context.name() != null) + name = (string) Visit(context.name()); + + AngularUnit au = null; + if (context.unit() != null) + { + var unit = (Unit) Visit(context.unit()); + if (unit != null && unit.Name.Equals("degree")) + { + au = new AngularUnit(unit.ConversionFactor, unit.Name, unit.Authority, unit.AuthorityCode, + string.Empty, string.Empty, string.Empty); + } + } + + HorizontalDatum datum = null; + if (context.datum() != null) + datum = (HorizontalDatum) Visit(context.datum()); + + PrimeMeridian pm = null; + if (context.primem() != null) + pm = (PrimeMeridian) Visit(context.primem()); + + //This is default axis values if not specified. + var axisCtx = context.axis(); + var ax1 = axisCtx.Length > 0 ? (AxisInfo) Visit(axisCtx[0]) : new AxisInfo("Lon", AxisOrientationEnum.East); + var ax2 = axisCtx.Length > 1 ? (AxisInfo) Visit(axisCtx[1]) : new AxisInfo("Lat", AxisOrientationEnum.North); + + Authority authority = null; + if (context.authority() != null) + { + authority = (Authority) Visit(context.authority()); + } + + var result = this.factory.CreateGeographicCoordinateSystem(name, au, (HorizontalDatum)datum, + (PrimeMeridian)pm, axis0: ax1, axis1: ax2); + + if (authority is Authority authObj) + { + result.AuthorityCode = authObj.Code; + result.Authority = authObj.Name; + } + + return result; + } + + public override object VisitGeoccs(WktCrsParser.GeoccsContext context) + { + string name = string.Empty; + if (context.name() != null) + name = (string) Visit(context.name()); + + HorizontalDatum datum = null; + if (context.datum() != null) + datum = (HorizontalDatum) Visit(context.datum()); + + PrimeMeridian meridian = null; + if (context.primem() != null) + meridian = (PrimeMeridian) Visit(context.primem()); + + var lu = (LinearUnit) null; + if (context.unit() != null) + { + var u = (ProjNet.CoordinateSystems.Unit) Visit(context.unit()); + if (u != null) + { + lu = new LinearUnit(u.ConversionFactor, u.Name, u.Authority, u.AuthorityCode, string.Empty, + string.Empty, string.Empty); + } + } + + Authority authority = null; + if (context.authority() != null) + { + authority = (Authority) Visit(context.authority()); + } + + 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 override object VisitProjcs(WktCrsParser.ProjcsContext context) + { + string name = string.Empty; + if (context.name() != null) + name = (string) Visit(context.name()); + + GeographicCoordinateSystem gcs = null; + if (context.geogcs() != null) + gcs = (GeographicCoordinateSystem) Visit(context.geogcs()); + + var lu = (LinearUnit) null; + if (context.unit() != null) + { + var u = (ProjNet.CoordinateSystems.Unit) Visit(context.unit()); + if (u != null) + { + lu = new LinearUnit(u.ConversionFactor, u.Name, u.Authority, u.AuthorityCode, string.Empty, + string.Empty, string.Empty); + } + } + + Projection p = null; + if (context.projection() != null) + p = (Projection) Visit(context.projection()); + + + var axisCtx = context.axis(); + var ax1 = axisCtx.Length > 0 ? (AxisInfo) Visit(axisCtx[0]) : new AxisInfo("X", AxisOrientationEnum.East); + var ax2 = axisCtx.Length > 1 ? (AxisInfo) Visit(axisCtx[1]) : new AxisInfo("Y", AxisOrientationEnum.North); + var aa = new List {ax1, ax2}; + + Authority authority = null; + if (context.authority() != null) + { + authority = (Authority) Visit(context.authority()); + } + + var result = new ProjectedCoordinateSystem(gcs.HorizontalDatum, gcs, lu, p, + aa, name, authority?.Name, authority!=null?authority.Code:-1, string.Empty, string.Empty, string.Empty); + + return result; + } + + public WktCrsParser.WktContext ParseAndBuild(string input) + { + var stream = CharStreams.fromString(input); + var lexer = new WktCrsLexer(stream); + var tokens = new CommonTokenStream(lexer); + var parser = new WktCrsParser(tokens); + + var wktContext = parser.wkt(); + this.Visit(wktContext); + + return wktContext; + } + } +} diff --git a/src/ProjNet/ProjNET.csproj b/src/ProjNet/ProjNET.csproj index e5cfcda..442da61 100644 --- a/src/ProjNet/ProjNET.csproj +++ b/src/ProjNet/ProjNET.csproj @@ -25,7 +25,19 @@ Proj.NET performs point-to-point coordinate conversions between geodetic coordin - + + false + true + false + ProjNet.IO.Wkt + true + + + + + + + diff --git a/test/ProjNet.Tests/WKT/WktToProjBuilderTests.cs b/test/ProjNet.Tests/WKT/WktToProjBuilderTests.cs new file mode 100644 index 0000000..5bc7e2f --- /dev/null +++ b/test/ProjNet.Tests/WKT/WktToProjBuilderTests.cs @@ -0,0 +1,105 @@ +using NUnit.Framework; +using ProjNet.CoordinateSystems; +using ProjNet.IO.Wkt; + +namespace ProjNET.Tests.WKT; + +public class WktToProjBuilderTests +{ + + private readonly CoordinateSystemFactory _coordinateSystemFactory = new CoordinateSystemFactory(); + + private readonly WktToProjBuilder _wktBuilder = new WktToProjBuilder(); + + + [Test] + public void TestProjectedCoordinateSystem_EPSG_2918() + { + const string wkt = "PROJCS[\"NAD83(HARN) / Texas Central (ftUS)\", "+ + "GEOGCS[\"NAD83(HARN)\", " + + "DATUM[\"NAD83_High_Accuracy_Regional_Network\", "+ + "SPHEROID[\"GRS 1980\", 6378137, 298.257222101, AUTHORITY[\"EPSG\", \"7019\"]], "+ + "TOWGS84[725, 685, 536, 0, 0, 0, 0], " + + "AUTHORITY[\"EPSG\", \"6152\"]], "+ + "PRIMEM[\"Greenwich\", 0, AUTHORITY[\"EPSG\", \"8901\"]], "+ + "UNIT[\"degree\", 0.0174532925199433, AUTHORITY[\"EPSG\", \"9122\"]], "+ + "AUTHORITY[\"EPSG\", \"4152\"]], "+ + "PROJECTION[\"Lambert_Conformal_Conic_2SP\"], " + + "PARAMETER[\"standard_parallel_1\", 31.883333333333], " + + "PARAMETER[\"standard_parallel_2\", 30.1166666667], " + + "PARAMETER[\"latitude_of_origin\", 29.6666666667], " + + "PARAMETER[\"central_meridian\", -100.333333333333], " + + "PARAMETER[\"false_easting\", 2296583.333], " + + "PARAMETER[\"false_northing\", 9842500], " + + "UNIT[\"US survey foot\", 0.304800609601219, AUTHORITY[\"EPSG\", \"9003\"]], "+ + "AUTHORITY[\"EPSG\", \"2918\"]]"; + + _wktBuilder.ParseAndBuild(wkt); + } + + + /// + /// This test reads in a file with 2671 pre-defined coordinate systems and projections, + /// and tries to parse them. + /// + [Test] + public void ParseAllWKTs() + { + 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"); + } + + /// + /// This test reads in a file with 2671 pre-defined coordinate systems and projections, + /// and tries to parse them. + /// + [Test] + public void ParseAllWKTs_ANTLR() + { + int parseCount = 0; + foreach (var wkt in SRIDReader.GetSrids()) + { + var cs1 = _wktBuilder.ParseAndBuild(wkt.Wkt); + Assert.IsNotNull(cs1, "Could not parse WKT: " + wkt); + var cs2 = _wktBuilder.ParseAndBuild(wkt.Wkt.Replace("[", "(").Replace("]", ")")); + //Assert.That(cs1.Equals(cs2), Is.True); + parseCount++; + } + Assert.That(parseCount, Is.GreaterThan(2671), "Not all WKT was parsed"); + } + + + + [Test] + public void ParseProjCSWithExtension() + { + string wkt = "PROJCS[\"Google Maps Global Mercator\"," + + "GEOGCS[\"WGS 84\"," + + "DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]]," + + "AUTHORITY[\"EPSG\",\"6326\"]]," + + "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]]," + + "UNIT[\"degree\",0.01745329251994328,AUTHORITY[\"EPSG\",\"9122\"]]," + + "AUTHORITY[\"EPSG\",\"4326\"]]," + + "PROJECTION[\"Mercator_2SP\"]," + + "PARAMETER[\"standard_parallel_1\",0]," + + "PARAMETER[\"latitude_of_origin\",0]," + + "PARAMETER[\"central_meridian\",0]," + + "PARAMETER[\"false_easting\",0]," + + "PARAMETER[\"false_northing\",0]," + + "UNIT[\"Meter\",1]," + + "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\"]," + + "AUTHORITY[\"EPSG\",\"900913\"]]"; + + var cs1 = _wktBuilder.ParseAndBuild(wkt); + + Assert.IsNotNull(cs1); + } +} From cea9f315aaa1995d9beb2f9ad086324c030c77e3 Mon Sep 17 00:00:00 2001 From: Henry Roeland Date: Sat, 3 Aug 2024 21:10:59 +0200 Subject: [PATCH 2/2] Fixed last errors and did some optimization. --- .../CoordinateSystemFactory.cs | 39 +- src/ProjNet/IO/Wkt/WktCrs.g4 | 19 +- src/ProjNet/IO/Wkt/WktToProjBuilder.cs | 1244 +++++++++++++---- .../WKT/WktToProjBuilderTests.cs | 92 +- 4 files changed, 1078 insertions(+), 316 deletions(-) diff --git a/src/ProjNet/CoordinateSystems/CoordinateSystemFactory.cs b/src/ProjNet/CoordinateSystems/CoordinateSystemFactory.cs index 28d831d..d9e0b69 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,13 @@ // 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.Text; using ProjNet.IO.CoordinateSystems; +using ProjNet.IO.Wkt; namespace ProjNet.CoordinateSystems { @@ -26,15 +27,15 @@ namespace ProjNet.CoordinateSystems /// Builds up complex objects from simpler objects or values. /// /// - /// CoordinateSystemFactory allows applications to make coordinate systems that + /// CoordinateSystemFactory allows applications to make coordinate systems that /// is very flexible, whereas the other factories are easier to use. /// So this Factory can be used to make 'special' coordinate systems. - /// 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 + /// 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. /// - public class CoordinateSystemFactory + public class CoordinateSystemFactory { /// /// Creates an instance of this class @@ -64,6 +65,12 @@ public CoordinateSystem CreateFromWkt(string WKT) return info as CoordinateSystem; } + public CoordinateSystem CreateFromWktNew(string WKT) + { + var info = WktToProjBuilder.ParseAndBuild(WKT); + return info as CoordinateSystem; + } + /// /// Creates a [NOT IMPLEMENTED]. @@ -83,11 +90,11 @@ public CompoundCoordinateSystem CreateCompoundCoordinateSystem(string name, Coor /// /// Creates a . /// - /// The units of the axes in the fitted coordinate system will be + /// 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. /// Name of coordinate system /// Base coordinate system @@ -122,9 +129,9 @@ public FittedCoordinateSystem CreateFittedCoordinateSystem(string name, Coordina /// Creates a local coordinate system. /// /// - /// 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. /// /// Name of local coordinate system @@ -219,9 +226,9 @@ public IProjection CreateProjection(string name, string wktProjectionClass, List /// Creates from ellipsoid and Bursa-World parameters. /// /// - /// 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 horizontalDatumType of , or create it via WKT. /// /// Name of ellipsoid @@ -294,7 +301,7 @@ public ILocalDatum CreateLocalDatum(string name, DatumType datumType) /// /// Name of datum /// Type of datum - /// Vertical datum + /// Vertical datum public VerticalDatum CreateVerticalDatum(string name, DatumType datumType) { if (string.IsNullOrWhiteSpace(name)) @@ -322,7 +329,7 @@ public VerticalCoordinateSystem CreateVerticalCoordinateSystem(string name, Vert } /// - /// Creates a from a datum, + /// Creates a from a datum, /// linear unit and . /// /// Name of geocentric coordinate system diff --git a/src/ProjNet/IO/Wkt/WktCrs.g4 b/src/ProjNet/IO/Wkt/WktCrs.g4 index aad1ba1..103b652 100644 --- a/src/ProjNet/IO/Wkt/WktCrs.g4 +++ b/src/ProjNet/IO/Wkt/WktCrs.g4 @@ -45,18 +45,23 @@ wkt | vertcs | geoccs | localcs + | fittedcs ; +fittedcs + : 'FITTED_CS' LPAR name COMMA paramsmt COMMA projcs (COMMA authority)? RPAR + ; + +paramsmt + : 'PARAM_MT' LPAR name (COMMA parameter)+ RPAR + ; + compdcs : 'COMPD_CS' LPAR name COMMA (projcs | geogcs) COMMA vertcs COMMA authority RPAR ; projcs - : 'PROJCS' LPAR name COMMA geogcs COMMA projection COMMA (parameter COMMA)+ unit COMMA ( - extension COMMA - )? ( - axis COMMA - )* authority RPAR + : 'PROJCS' LPAR name COMMA geogcs COMMA projection COMMA (parameter COMMA)+ unit COMMA ((extension COMMA) | (axis COMMA))* authority RPAR ; geoccs @@ -76,7 +81,7 @@ localcs ; datum - : 'DATUM' LPAR name COMMA spheroid (COMMA towgs84)? (COMMA authority)? RPAR + : 'DATUM' LPAR name COMMA spheroid ((COMMA towgs84) | (COMMA authority))* RPAR ; vertdatum @@ -96,7 +101,7 @@ towgs84 ; extension - : 'EXTENSION' LPAR name COMMA projtext RPAR {} + : 'EXTENSION' LPAR name COMMA projtext RPAR ; authority diff --git a/src/ProjNet/IO/Wkt/WktToProjBuilder.cs b/src/ProjNet/IO/Wkt/WktToProjBuilder.cs index f80244e..7f444fd 100644 --- a/src/ProjNet/IO/Wkt/WktToProjBuilder.cs +++ b/src/ProjNet/IO/Wkt/WktToProjBuilder.cs @@ -2,24 +2,32 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Text; using Antlr4.Runtime; +using Antlr4.Runtime.Atn; using Antlr4.Runtime.Misc; -using Antlr4.Runtime.Tree; using ProjNet.CoordinateSystems; +using ProjNet.CoordinateSystems.Transformations; namespace ProjNet.IO.Wkt { - public class WktToProjBuilder : WktCrsBaseVisitor + /// + /// WktToProjBuilder + /// + public partial class WktToProjBuilder { private readonly CoordinateSystemFactory factory; + /// + /// Constructor. + /// public WktToProjBuilder() { factory = new CoordinateSystemFactory(); } - public class Authority + internal class Authority { public string Name { get; set; } @@ -27,448 +35,1110 @@ public class Authority } - public override object VisitName(WktCrsParser.NameContext context) + internal class NameVisitor : WktCrsBaseVisitor { - return context.GetText().Trim(new char[] {'\"', ' '}); + public static readonly NameVisitor Instance = new NameVisitor(); + + public override string VisitName(WktCrsParser.NameContext context) + { + return context.GetText().Trim(new char[] {'\"', ' '}); + } } - public override object VisitValue(WktCrsParser.ValueContext context) + + internal class ValueVisitor : WktCrsBaseVisitor { - string valueStr = context.GetText(); - if (double.TryParse(valueStr, NumberStyles.Any, CultureInfo.InvariantCulture, out double d)) - return d; + public static readonly ValueVisitor Instance = new ValueVisitor(); - return double.NaN; + public override double VisitValue(WktCrsParser.ValueContext context) + { + string valueStr = context.GetText(); + if (double.TryParse(valueStr, NumberStyles.Any, CultureInfo.InvariantCulture, out double d)) + return d; + + return double.NaN; + } } - public override object VisitLongitude(WktCrsParser.LongitudeContext context) + + /// + /// LongitudeVisitor + /// + public class LongitudeVisitor : WktCrsBaseVisitor { - string valueStr = context.GetText(); - if (double.TryParse(valueStr, NumberStyles.Any, CultureInfo.InvariantCulture, out double d)) - return d; + /// + /// VisitLongitude + /// + /// + /// + public override double VisitLongitude(WktCrsParser.LongitudeContext context) + { + string valueStr = context.GetText(); + if (double.TryParse(valueStr, NumberStyles.Any, CultureInfo.InvariantCulture, out double d)) + return d; - return double.NaN; + return double.NaN; + } } - public override object VisitAuthorityName(WktCrsParser.AuthorityNameContext context) + internal class AuthorityNameVisitor : WktCrsBaseVisitor { - return context.GetText().Trim(new char[] {'\"', ' '}); + public static readonly AuthorityNameVisitor Instance = new AuthorityNameVisitor(); + + public override string VisitAuthorityName(WktCrsParser.AuthorityNameContext context) + { + return context.GetText().Trim(new char[] {'\"', ' '}); + } } - public override object VisitCode(WktCrsParser.CodeContext context) + + internal class AuthorityCodeVisitor : WktCrsBaseVisitor { - if (!context.IsEmpty) + public static readonly AuthorityCodeVisitor Instance = new AuthorityCodeVisitor(); + + public override int VisitCode(WktCrsParser.CodeContext context) { - string codeStr = context.GetText(); - codeStr = codeStr.Trim(new char[] {' ', '\"'}); - if (codeStr.Contains("_")) + if (!context.IsEmpty) { - codeStr = codeStr.Substring(0, codeStr.IndexOf('_')); + string codeStr = context.GetText(); + codeStr = codeStr.Trim(new char[] {' ', '\"'}); + if (codeStr.Contains("_")) + { + codeStr = codeStr.Substring(0, codeStr.IndexOf('_')); + } + + if (!string.IsNullOrWhiteSpace(codeStr) && int.TryParse(codeStr, NumberStyles.Any, + CultureInfo.InvariantCulture, out int nmbr)) + { + return nmbr; + } } - if (!string.IsNullOrWhiteSpace(codeStr) && int.TryParse(codeStr, NumberStyles.Any, CultureInfo.InvariantCulture, out int nmbr)) + return -1; + } + } + + + internal class ProjTextVisitor : WktCrsBaseVisitor + { + public override string VisitProjtext(WktCrsParser.ProjtextContext context) + { + string str = context.GetText(); + str = str.Trim(new char[] {' ', '\"'}); + return str; + } + } + + internal class AxisOrientVisitor : WktCrsBaseVisitor + { + public override AxisOrientationEnum VisitAxisOrient(WktCrsParser.AxisOrientContext context) + { + + if (!context.IsEmpty) { - return nmbr; + string direction = context.GetText().Trim(new char[] {' ', '\"'}); + if (Enum.TryParse(direction, true, out AxisOrientationEnum enumVal)) + { + return enumVal; + } } - } - return -1; + return AxisOrientationEnum.Other; + } } - public override object VisitAxisOrient(WktCrsParser.AxisOrientContext context) + + internal class AuthorityVisitor : WktCrsBaseVisitor { + public static readonly AuthorityVisitor Instance = new AuthorityVisitor(); - if (!context.IsEmpty) + public override Authority VisitAuthority([NotNull] WktCrsParser.AuthorityContext context) { - string direction = context.GetText().Trim(new char[]{' ', '\"'}); - if (Enum.TryParse(direction, true, out AxisOrientationEnum enumVal)) + string authName = string.Empty; + var authNameCtx = context.authorityName(); + if (authNameCtx != null) { - return enumVal; + var visitor = AuthorityNameVisitor.Instance; + authName = visitor.VisitAuthorityName(authNameCtx); + } + + int code = -1; + var authCodeCtx = context.code(); + if (authCodeCtx != null) + { + var visitor = AuthorityCodeVisitor.Instance; + code = visitor.VisitCode(authCodeCtx); } - } - return AxisOrientationEnum.Other; + return new Authority {Name = authName, Code = code}; + } } - public override object VisitAuthority([NotNull] WktCrsParser.AuthorityContext context) + internal class AxisVisitor : WktCrsBaseVisitor { - string authName = string.Empty; - if (context.authorityName() != null) - authName = (string)Visit(context.authorityName()); + public static readonly AxisVisitor Instance = new AxisVisitor(); - int code = -1; - if (context.code() != null) - code = (int) Visit(context.code()); + public override AxisInfo VisitAxis(WktCrsParser.AxisContext context) + { + string name = string.Empty; + var nameCtx = context.name(); + if (nameCtx != null) + { + var visitor = NameVisitor.Instance; + name = visitor.VisitName(nameCtx); + } - return new Authority {Name = authName, Code = code}; + var orient = AxisOrientationEnum.Other; + var axisOrientCtx = context.axisOrient(); + if (axisOrientCtx != null) + { + var visitor = new AxisOrientVisitor(); + orient = visitor.VisitAxisOrient(axisOrientCtx); + } + + return new AxisInfo(name, orient); + } } - public override object VisitAxis(WktCrsParser.AxisContext context) + internal class ExtensionVisitor : WktCrsBaseVisitor<(string, string)> { - string name = string.Empty; - if (context.name()!=null) - name = (string) Visit(context.name()); + public static readonly ExtensionVisitor Instance = new ExtensionVisitor(); - var orient = AxisOrientationEnum.Other; - if (context.axisOrient()!=null) - orient = (AxisOrientationEnum) Visit(context.axisOrient()); + public override (string, string) VisitExtension([NotNull] WktCrsParser.ExtensionContext context) + { + string name = string.Empty; + var nameCtx = context.name(); + if (nameCtx != null) + { + var visitor = NameVisitor.Instance; + name = visitor.VisitName(nameCtx); + } + + string projText = string.Empty; + var projCtx = context.projtext(); + if (projCtx != null) + { + var visitor = new ProjTextVisitor(); + projText = visitor.VisitProjtext(projCtx); + } - return new AxisInfo(name, orient); + // No ProjNet object for Extension so returning a tuple. + return (name, projText); + } } - public override object VisitExtension([NotNull] WktCrsParser.ExtensionContext context) - { - string name = string.Empty; - if (context.name() != null) - name = (string) Visit(context.name()); - string projText = string.Empty; - if (context.projtext() != null) - projText = (string) Visit(context.projtext()); + internal class ToWgs84Visitor : WktCrsBaseVisitor + { + public static readonly ToWgs84Visitor Instance = new ToWgs84Visitor(); - // No ProjNet object for Extension so returning a tuple. - return (name, projText); + public override Wgs84ConversionInfo VisitTowgs84(WktCrsParser.Towgs84Context context) + { + double dx = 0.0d; + var dxCtx = context.dx(); + if (!dxCtx.IsEmpty && double.TryParse(dxCtx.GetText(), NumberStyles.Any, + CultureInfo.InvariantCulture, out double dxResult)) + dx = dxResult; + + double dy = 0.0d; + var dyCtx = context.dy(); + if (!dyCtx.IsEmpty && double.TryParse(dyCtx.GetText(), NumberStyles.Any, + CultureInfo.InvariantCulture, out double dyResult)) + dy = dyResult; + + double dz = 0.0d; + var dzCtx = context.dz(); + if (!dzCtx.IsEmpty && double.TryParse(dzCtx.GetText(), NumberStyles.Any, + CultureInfo.InvariantCulture, out double dzResult)) + dz = dzResult; + + double ex = 0.0d; + var exCtx = context.ex(); + if (!exCtx.IsEmpty && double.TryParse(exCtx.GetText(), NumberStyles.Any, + CultureInfo.InvariantCulture, out double exResult)) + ex = exResult; + + double ey = 0.0d; + var eyCtx = context.ey(); + if (!eyCtx.IsEmpty && double.TryParse(eyCtx.GetText(), NumberStyles.Any, + CultureInfo.InvariantCulture, out double eyResult)) + ey = eyResult; + + double ez = 0.0d; + var ezCtx = context.ez(); + if (!ezCtx.IsEmpty && double.TryParse(ezCtx.GetText(), NumberStyles.Any, + CultureInfo.InvariantCulture, out double ezResult)) + ez = ezResult; + + double ppm = 0.0d; + var ppmCtx = context.ppm(); + if (ppmCtx.IsEmpty && double.TryParse(ppmCtx.GetText(), NumberStyles.Any, + CultureInfo.InvariantCulture, out double ppmResult)) + ppm = ppmResult; + + return new Wgs84ConversionInfo(dx, dy, dz, ex, ey, ez, ppm); + } } - public override object VisitTowgs84(WktCrsParser.Towgs84Context context) + + internal class ProjectionVisitor : WktCrsBaseVisitor { - double dx = 0.0d; - if (!context.dx().IsEmpty && double.TryParse(context.dx().GetText(), NumberStyles.Any, CultureInfo.InvariantCulture, out double dxResult )) - dx = dxResult; + public static readonly ProjectionVisitor Instance = new ProjectionVisitor(); - double dy = 0.0d; - if (!context.dy().IsEmpty && double.TryParse(context.dy().GetText(), NumberStyles.Any, CultureInfo.InvariantCulture, out double dyResult )) - dy = dyResult; + public override Projection VisitProjection(WktCrsParser.ProjectionContext context) + { + string name = string.Empty; + var nameCtx = context.name(); + if (nameCtx != null) + { + var visitor = NameVisitor.Instance; + name = visitor.VisitName(nameCtx); + } - double dz = 0.0d; - if (!context.dz().IsEmpty && double.TryParse(context.dz().GetText(), NumberStyles.Any, CultureInfo.InvariantCulture, out double dzResult )) - dz = dzResult; + Authority authority = null; + var authorityCtx = context.authority(); + if (authorityCtx != null) + { + var visitor = AuthorityVisitor.Instance; + authority = visitor.VisitAuthority(authorityCtx); + } - double ex = 0.0d; - if (!context.ex().IsEmpty && double.TryParse(context.ex().GetText(), NumberStyles.Any, CultureInfo.InvariantCulture, out double exResult )) - ex = exResult; + return new Projection(name, new List(), name, authority?.Name, + authority != null ? authority.Code : -1, string.Empty, string.Empty, string.Empty); + } + } - double ey = 0.0d; - if (!context.ey().IsEmpty && double.TryParse(context.ey().GetText(), NumberStyles.Any, CultureInfo.InvariantCulture, out double eyResult )) - ey = eyResult; + internal class ProjectionParameterVisitor : WktCrsBaseVisitor + { + public static readonly ProjectionParameterVisitor Instance = new ProjectionParameterVisitor(); - double ez = 0.0d; - if (!context.ez().IsEmpty && double.TryParse(context.ez().GetText(), NumberStyles.Any, CultureInfo.InvariantCulture, out double ezResult )) - ez = ezResult; + public override ProjectionParameter VisitParameter(WktCrsParser.ParameterContext context) + { + string name = string.Empty; + var nameCtx = context.name(); + if (nameCtx != null) + { + var visitor = NameVisitor.Instance; + name = visitor.VisitName(nameCtx); + } - double ppm = 0.0d; - if (!context.ppm().IsEmpty && double.TryParse(context.ppm().GetText(), NumberStyles.Any, CultureInfo.InvariantCulture, out double ppmResult )) - ppm = ppmResult; + double value = double.NaN; + var valueCtx = context.value(); + if (valueCtx != null) + { + var visitor = ValueVisitor.Instance; + value = visitor.VisitValue(valueCtx); + } - return new Wgs84ConversionInfo(dx, dy, dz, ex, ey, ez, ppm); + return new ProjectionParameter(name, value); + } } - public override object VisitProjection(WktCrsParser.ProjectionContext context) + internal class ParameterVisitor : WktCrsBaseVisitor { - string name = string.Empty; - if (context.name() != null) - name = (string) Visit(context.name()); + public static readonly ParameterVisitor Instance = new ParameterVisitor(); - Authority authority = null; - if (context.authority() != null) - authority = (Authority) Visit(context.authority()); + public override Parameter VisitParameter(WktCrsParser.ParameterContext context) + { + string name = string.Empty; + var nameCtx = context.name(); + if (nameCtx != null) + { + var visitor = NameVisitor.Instance; + name = visitor.VisitName(nameCtx); + } - return new Projection(name, new List(), name, authority?.Name, authority!=null ? authority.Code:-1, string.Empty, string.Empty, string.Empty); + double value = double.NaN; + var valueCtx = context.value(); + if (valueCtx != null) + { + var visitor = ValueVisitor.Instance; + value = visitor.VisitValue(valueCtx); + } + + return new Parameter(name, value); + } } - public override object VisitParameter(WktCrsParser.ParameterContext context) + internal class SemiMajorAxisVisitor : WktCrsBaseVisitor { - string name = string.Empty; - if (context.name() != null) - name = (string) Visit(context.name()); + public static readonly SemiMajorAxisVisitor Instance = new SemiMajorAxisVisitor(); - double value = double.NaN; - if (context.value() != null) - value = (double) Visit(context.value()); + public override double VisitSemiMajorAxis(WktCrsParser.SemiMajorAxisContext context) + { + if (!context.IsEmpty && double.TryParse(context.GetText(), NumberStyles.Any, + CultureInfo.InvariantCulture, out double d)) + return d; + return double.NaN; + } + } + + internal class InverseFlatteningVisitor : WktCrsBaseVisitor + { + public static readonly InverseFlatteningVisitor Instance = new InverseFlatteningVisitor(); - return new ProjectionParameter(name, value); + public override double VisitInverseFlattening(WktCrsParser.InverseFlatteningContext context) + { + if (!context.IsEmpty && double.TryParse(context.GetText(), NumberStyles.Any, + CultureInfo.InvariantCulture, out double d)) + return d; + return double.NaN; + } } - public override object VisitSpheroid(WktCrsParser.SpheroidContext context) + internal class SpheroidVisitor : WktCrsBaseVisitor { - string name = string.Empty; - if (context.name() != null) - name = (string) Visit(context.name()); + public static readonly SpheroidVisitor Instance = new SpheroidVisitor(); - double semiMajorAxis = double.NaN; - if (context.semiMajorAxis() != null) - semiMajorAxis = (double)Visit(context.semiMajorAxis()); + public override Ellipsoid VisitSpheroid(WktCrsParser.SpheroidContext context) + { + string name = string.Empty; + var nameCtx = context.name(); + if (nameCtx != null) + { + var visitor = NameVisitor.Instance; + name = visitor.VisitName(nameCtx); + } - double inverseFlattening = double.NaN; - if (context.inverseFlattening() != null) - inverseFlattening = (double)Visit(context.inverseFlattening()); + double semiMajorAxis = double.NaN; + var smaCtx = context.semiMajorAxis(); + if (smaCtx != null) + { + var visitor = new SemiMajorAxisVisitor(); + semiMajorAxis = visitor.VisitSemiMajorAxis(smaCtx); + } - Authority authority = null; - if (context.authority() != null) - authority = (Authority) Visit(context.authority()); + double inverseFlattening = double.NaN; + var invfCtx = context.inverseFlattening(); + if (invfCtx != null) + { + var visitor = new InverseFlatteningVisitor(); + inverseFlattening = visitor.VisitInverseFlattening(invfCtx); + } - return new Ellipsoid(semiMajorAxis, 0.0, inverseFlattening, true, LinearUnit.Metre, name, - authority?.Name, authority!=null?authority.Code:-1, string.Empty, string.Empty, string.Empty); + Authority authority = null; + var authCtx = context.authority(); + if (authCtx != null) + { + var visitor = AuthorityVisitor.Instance; + authority = visitor.Visit(authCtx); + } - } + return new Ellipsoid(semiMajorAxis, 0.0, inverseFlattening, true, LinearUnit.Metre, name, + authority?.Name, authority != null ? authority.Code : -1, string.Empty, string.Empty, string.Empty); - public override object VisitSemiMajorAxis(WktCrsParser.SemiMajorAxisContext context) - { - if (!context.IsEmpty && double.TryParse(context.GetText(), NumberStyles.Any, CultureInfo.InvariantCulture, out double d)) - return d; - return double.NaN; + } } - public override object VisitInverseFlattening(WktCrsParser.InverseFlatteningContext context) - { - if (!context.IsEmpty && double.TryParse(context.GetText(), NumberStyles.Any, CultureInfo.InvariantCulture, out double d)) - return d; - return double.NaN; - } - public override object VisitConversionFactor(WktCrsParser.ConversionFactorContext context) + internal class ConversionFactorVisitor : WktCrsBaseVisitor { - if (!context.IsEmpty && double.TryParse(context.GetText(), NumberStyles.Any, CultureInfo.InvariantCulture, out double d)) - return d; - return double.NaN; + public override double VisitConversionFactor(WktCrsParser.ConversionFactorContext context) + { + if (!context.IsEmpty && double.TryParse(context.GetText(), NumberStyles.Any, + CultureInfo.InvariantCulture, out double d)) + return d; + return double.NaN; + } } - public override object VisitDatum(WktCrsParser.DatumContext context) + internal class DatumVisitor : WktCrsBaseVisitor { - string name = string.Empty; - if (context.name() != null) - name = (string) Visit(context.name()); + public static readonly DatumVisitor Instance = new DatumVisitor(); - Ellipsoid spheroid = null; - if (context.spheroid() != null) - spheroid = (Ellipsoid) Visit(context.spheroid()); + private readonly CoordinateSystemFactory factory = new CoordinateSystemFactory(); - Wgs84ConversionInfo wgs84 = null; - if (context.towgs84() != null) - wgs84 = (Wgs84ConversionInfo) Visit(context.towgs84()); - - Authority authority = null; - if (context.authority() != null) + public override HorizontalDatum VisitDatum(WktCrsParser.DatumContext context) { - authority = (Authority) Visit(context.authority()); - } + string name = string.Empty; + var nameCtx = context.name(); + if (nameCtx != null) + { + var visitor = NameVisitor.Instance; + name = visitor.VisitName(nameCtx); + } - var result = this.factory.CreateHorizontalDatum( - name, DatumType.HD_Geocentric, ellipsoid: spheroid, toWgs84: wgs84); + Ellipsoid spheroid = null; + var spheroidCtx = context.spheroid(); + if (spheroidCtx != null) + { + var visitor = SpheroidVisitor.Instance; + spheroid = visitor.VisitSpheroid(spheroidCtx); + } - if (authority != null) - { - result.Authority = authority.Name; - result.AuthorityCode = authority.Code; - } + Wgs84ConversionInfo wgs84 = null; + var towgs84Ctx = context.towgs84(); + if (towgs84Ctx != null && towgs84Ctx.Length>0) + { + var visitor = new ToWgs84Visitor(); + wgs84 = visitor.VisitTowgs84(towgs84Ctx[0]); + } - return result; + Authority authority = null; + var authorityCtx = context.authority(); + if (authorityCtx != null && authorityCtx.Length>0) + { + var visitor = AuthorityVisitor.Instance; + authority = visitor.VisitAuthority(authorityCtx[0]); + } + + var result = this.factory.CreateHorizontalDatum( + name, DatumType.HD_Geocentric, ellipsoid: spheroid, toWgs84: wgs84); + + if (authority != null) + { + result.Authority = authority.Name; + result.AuthorityCode = authority.Code; + } + + return result; + } } - public override object VisitUnit(WktCrsParser.UnitContext context) + internal class UnitVisitor : WktCrsBaseVisitor { - string name = string.Empty; - if (context.name() != null) - name = (string) Visit(context.name()); - - double factor = double.NaN; - if (context.conversionFactor() != null) - factor = (double) Visit(context.conversionFactor()); + public override Unit VisitUnit(WktCrsParser.UnitContext context) + { + string name = string.Empty; + var nameCtx = context.name(); + if (nameCtx != null) + { + var visitor = NameVisitor.Instance; + name = visitor.VisitName(nameCtx); + } - Authority authority = null; - if (context.authority() != null) - authority = (Authority) Visit(context.authority()); + double factor = double.NaN; + var cfCtx = context.conversionFactor(); + if (cfCtx != null) + { + var visitor = new ConversionFactorVisitor(); + factor = visitor.VisitConversionFactor(cfCtx); + } + Authority authority = null; + var authorityCtx = context.authority(); + if (authorityCtx != null) + { + var visitor = AuthorityVisitor.Instance; + authority = visitor.VisitAuthority(authorityCtx); + } - return new Unit(factor,name, authority?.Name, authority!=null?authority.Code:-1 , string.Empty, string.Empty, string.Empty); + return new Unit(factor, name, authority?.Name, authority != null ? authority.Code : -1, string.Empty, + string.Empty, string.Empty); + } } - public override object VisitPrimem(WktCrsParser.PrimemContext context) + internal class PrimemVisitor : WktCrsBaseVisitor { - string name = string.Empty; - if (context.name() != null) - name = (string) Visit(context.name()); + public static readonly PrimemVisitor Instance = new PrimemVisitor(); - double longitude = double.NaN; - if (context.longitude() != null) - longitude = (double) Visit(context.longitude()); + private readonly CoordinateSystemFactory factory = new CoordinateSystemFactory(); - AngularUnit au = null; - if (context.unit() != null) + public override PrimeMeridian VisitPrimem(WktCrsParser.PrimemContext context) { - var unit = (Unit) Visit(context.unit()); - if (unit != null && unit.Name.Equals("degree")) + string name = string.Empty; + var nameCtx = context.name(); + if (nameCtx != null) { - au = new AngularUnit(unit.ConversionFactor, unit.Name, unit.Authority, unit.AuthorityCode, - string.Empty, string.Empty, string.Empty); + var visitor = NameVisitor.Instance; + name = visitor.VisitName(nameCtx); } - } + double longitude = double.NaN; + var ltCtx = context.longitude(); + if (ltCtx != null) + { + var visitor = new LongitudeVisitor(); + longitude = visitor.VisitLongitude(ltCtx); + } - Authority authority = null; - if (context.authority() != null) - { - authority = (Authority) Visit(context.authority()); + var au = AngularUnit.Degrees; + var unitCtx = context.unit(); + if (unitCtx != null) + { + var visitor = new UnitVisitor(); + var unit = visitor.VisitUnit(unitCtx); + if (unit != null && unit.Name.Equals("degree")) + { + au = new AngularUnit(unit.ConversionFactor, unit.Name, unit.Authority, unit.AuthorityCode, + string.Empty, string.Empty, string.Empty); + } + } + + Authority authority = null; + var authorityCtx = context.authority(); + if (authorityCtx != null) + { + var visitor = AuthorityVisitor.Instance; + authority = visitor.VisitAuthority(authorityCtx); + } + + 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; } + } - var result = this.factory.CreatePrimeMeridian(name, angularUnit: au, longitude: longitude); - if (authority is Authority authObj) + internal class GeographicCoordinateSystemVisitor : WktCrsBaseVisitor + { + public static readonly GeographicCoordinateSystemVisitor Instance = new GeographicCoordinateSystemVisitor(); + + private readonly CoordinateSystemFactory factory = new CoordinateSystemFactory(); + + public override GeographicCoordinateSystem VisitGeogcs(WktCrsParser.GeogcsContext context) { - result.AuthorityCode = authObj.Code; - result.Authority = authObj.Name; - } + string name = string.Empty; + var nameCtx = context.name(); + if (nameCtx != null) + { + var visitor = NameVisitor.Instance; + name = visitor.VisitName(nameCtx); + } + + var au = AngularUnit.Degrees; + var unitCtx = context.unit(); + if (unitCtx != null) + { + var visitor = new UnitVisitor(); + var unit = visitor.VisitUnit(unitCtx); + if (unit != null && unit.Name.Equals("degree")) + { + au = new AngularUnit(unit.ConversionFactor, unit.Name, unit.Authority, unit.AuthorityCode, + string.Empty, string.Empty, string.Empty); + } + } + + HorizontalDatum datum = null; + var datumCtx = context.datum(); + if (datumCtx != null) + { + var visitor = DatumVisitor.Instance; + datum = visitor.VisitDatum(datumCtx); + } + + PrimeMeridian pm = null; + var pmCtx = context.primem(); + if (pmCtx != null) + { + var visitor = PrimemVisitor.Instance; + pm = visitor.VisitPrimem(pmCtx); + } + + //This is default axis values if not specified. + var axVisitor = new AxisVisitor(); + var axisCtx = context.axis(); + var ax1 = axisCtx.Length > 0 + ? axVisitor.VisitAxis(axisCtx[0]) + : new AxisInfo("Lon", AxisOrientationEnum.East); + var ax2 = axisCtx.Length > 1 + ? axVisitor.VisitAxis(axisCtx[1]) + : new AxisInfo("Lat", AxisOrientationEnum.North); + + Authority authority = null; + var authorityCtx = context.authority(); + if (authorityCtx != null) + { + var visitor = AuthorityVisitor.Instance; + authority = visitor.VisitAuthority(authorityCtx); + } + + var result = this.factory.CreateGeographicCoordinateSystem(name, au, (HorizontalDatum) datum, + (PrimeMeridian) pm, axis0: ax1, axis1: ax2); - return result; + if (authority is Authority authObj) + { + result.AuthorityCode = authObj.Code; + result.Authority = authObj.Name; + } + + return result; + } } - public override object VisitGeogcs(WktCrsParser.GeogcsContext context) + internal class GeocentricCoordinateSystemVisitor : WktCrsBaseVisitor { - string name = string.Empty; - if (context.name() != null) - name = (string) Visit(context.name()); + public static readonly GeocentricCoordinateSystemVisitor Instance = new GeocentricCoordinateSystemVisitor(); - AngularUnit au = null; - if (context.unit() != null) + private readonly CoordinateSystemFactory factory = new CoordinateSystemFactory(); + + public override GeocentricCoordinateSystem VisitGeoccs(WktCrsParser.GeoccsContext context) { - var unit = (Unit) Visit(context.unit()); - if (unit != null && unit.Name.Equals("degree")) + string name = string.Empty; + var nameCtx = context.name(); + if (nameCtx != null) { - au = new AngularUnit(unit.ConversionFactor, unit.Name, unit.Authority, unit.AuthorityCode, - string.Empty, string.Empty, string.Empty); + var visitor = NameVisitor.Instance; + name = visitor.VisitName(nameCtx); } - } - HorizontalDatum datum = null; - if (context.datum() != null) - datum = (HorizontalDatum) Visit(context.datum()); + HorizontalDatum datum = null; + var datumCtx = context.datum(); + if (datumCtx != null) + { + var visitor = DatumVisitor.Instance; + datum = visitor.VisitDatum(datumCtx); + } - PrimeMeridian pm = null; - if (context.primem() != null) - pm = (PrimeMeridian) Visit(context.primem()); + PrimeMeridian meridian = null; + var pmCtx = context.primem(); + if (pmCtx != null) + { + var visitor = PrimemVisitor.Instance; + meridian = visitor.VisitPrimem(pmCtx); + } - //This is default axis values if not specified. - var axisCtx = context.axis(); - var ax1 = axisCtx.Length > 0 ? (AxisInfo) Visit(axisCtx[0]) : new AxisInfo("Lon", AxisOrientationEnum.East); - var ax2 = axisCtx.Length > 1 ? (AxisInfo) Visit(axisCtx[1]) : new AxisInfo("Lat", AxisOrientationEnum.North); + var lu = (LinearUnit) null; + var unitCtx = context.unit(); + if (unitCtx != null) + { + var visitor = new UnitVisitor(); + var u = visitor.VisitUnit(unitCtx); + if (u != null) + { + lu = new LinearUnit(u.ConversionFactor, u.Name, u.Authority, u.AuthorityCode, string.Empty, + string.Empty, string.Empty); + } + } - Authority authority = null; - if (context.authority() != null) - { - authority = (Authority) Visit(context.authority()); + Authority authority = null; + var authorityCtx = context.authority(); + if (authorityCtx != null) + { + var visitor = AuthorityVisitor.Instance; + authority = visitor.VisitAuthority(authorityCtx); + } + + 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; } + } - var result = this.factory.CreateGeographicCoordinateSystem(name, au, (HorizontalDatum)datum, - (PrimeMeridian)pm, axis0: ax1, axis1: ax2); - if (authority is Authority authObj) + internal class ProjectedCoordinateSystemVisitor : WktCrsBaseVisitor + { + public static readonly ProjectedCoordinateSystemVisitor Instance = new ProjectedCoordinateSystemVisitor(); + + public override ProjectedCoordinateSystem VisitProjcs(WktCrsParser.ProjcsContext context) { - result.AuthorityCode = authObj.Code; - result.Authority = authObj.Name; - } + string name = string.Empty; + var nameCtx = context.name(); + if (nameCtx != null) + { + var visitor = NameVisitor.Instance; + name = visitor.VisitName(nameCtx); + } + + GeographicCoordinateSystem gcs = null; + var gcsCtx = context.geogcs(); + if (gcsCtx != null) + { + var visitor = GeographicCoordinateSystemVisitor.Instance; + gcs = visitor.VisitGeogcs(gcsCtx); + } + + var lu = (LinearUnit) null; + var unitCtx = context.unit(); + if (unitCtx != null) + { + var visitor = new UnitVisitor(); + var u = visitor.VisitUnit(unitCtx); + if (u != null) + { + lu = new LinearUnit(u.ConversionFactor, u.Name, u.Authority, u.AuthorityCode, string.Empty, + string.Empty, string.Empty); + } + } + + Projection p = null; + var projCtx = context.projection(); + if (projCtx != null) + { + var visitor = new ProjectionVisitor(); + p = visitor.VisitProjection(projCtx); + } + + + var axVisitor = new AxisVisitor(); + var axisCtx = context.axis(); + var ax1 = axisCtx.Length > 0 + ? axVisitor.VisitAxis(axisCtx[0]) + : new AxisInfo("X", AxisOrientationEnum.East); + var ax2 = axisCtx.Length > 1 + ? axVisitor.VisitAxis(axisCtx[1]) + : new AxisInfo("Y", AxisOrientationEnum.North); + var aa = new List {ax1, ax2}; + + Authority authority = null; + var authorityCtx = context.authority(); + if (authorityCtx != null) + { + var visitor = AuthorityVisitor.Instance; + authority = visitor.VisitAuthority(authorityCtx); + } + + var result = new ProjectedCoordinateSystem(gcs.HorizontalDatum, gcs, lu, p, + aa, name, authority?.Name, authority != null ? authority.Code : -1, string.Empty, string.Empty, + string.Empty); - return result; + return result; + } } - public override object VisitGeoccs(WktCrsParser.GeoccsContext context) + internal class ParamsMathTransformVisitor : WktCrsBaseVisitor { - string name = string.Empty; - if (context.name() != null) - name = (string) Visit(context.name()); + public static readonly ParamsMathTransformVisitor Instance = new ParamsMathTransformVisitor(); - HorizontalDatum datum = null; - if (context.datum() != null) - datum = (HorizontalDatum) Visit(context.datum()); + public override AffineTransform VisitParamsmt(WktCrsParser.ParamsmtContext context) + { + string name = ""; + var nameCtx = context.name(); + if (nameCtx != null) + { + var visitor = NameVisitor.Instance; + name = visitor.VisitName(nameCtx); + } - PrimeMeridian meridian = null; - if (context.primem() != null) - meridian = (PrimeMeridian) Visit(context.primem()); + var parameters = new List(); + var paramCtx = context.parameter(); + if (paramCtx != null) + { + var visitor = new ParameterVisitor(); + parameters = paramCtx.Select(pc => visitor.VisitParameter(pc)).ToList(); + } - var lu = (LinearUnit) null; - if (context.unit() != null) - { - var u = (ProjNet.CoordinateSystems.Unit) Visit(context.unit()); - if (u != null) + if (name.Equals("Affine", StringComparison.InvariantCultureIgnoreCase) && parameters.Any()) { - lu = new LinearUnit(u.ConversionFactor, u.Name, u.Authority, u.AuthorityCode, string.Empty, - string.Empty, string.Empty); + /* + 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 = parameters; + 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; } + } - Authority authority = null; - if (context.authority() != null) + + internal class FittedCoordinateSystemVisitor : WktCrsBaseVisitor + { + public static readonly FittedCoordinateSystemVisitor Instance = new FittedCoordinateSystemVisitor(); + + public override FittedCoordinateSystem VisitFittedcs(WktCrsParser.FittedcsContext context) { - authority = (Authority) Visit(context.authority()); + string name = string.Empty; + var nameCtx = context.name(); + if (nameCtx != null) + { + var visitor = NameVisitor.Instance; + name = visitor.VisitName(nameCtx); + } + + MathTransform mathTransform = null; + var pmtCtx = context.paramsmt(); + if (pmtCtx != null) + { + var visitor = new ParamsMathTransformVisitor(); + mathTransform = visitor.VisitParamsmt(pmtCtx); + } + + ProjectedCoordinateSystem baseCS = null; + var projcsCtx = context.projcs(); + if (projcsCtx != null) + { + var visitor = new ProjectedCoordinateSystemVisitor(); + baseCS = visitor.VisitProjcs(projcsCtx); + } + + Authority authority = null; + var authorityCtx = context.authority(); + if (authorityCtx != null) + { + var visitor = AuthorityVisitor.Instance; + authority = visitor.VisitAuthority(authorityCtx); + } + + var result = new FittedCoordinateSystem(baseCS, mathTransform, name, authority?.Name, + authority != null ? authority.Code : -1, string.Empty, string.Empty, string.Empty); + + return result; } + } + - var result = - this.factory.CreateGeocentricCoordinateSystem(name, (HorizontalDatum)datum, lu, (PrimeMeridian)meridian); + internal class WktCrsVisitor : WktCrsBaseVisitor + { + public static readonly WktCrsVisitor Instance = new WktCrsVisitor(); - if (authority is Authority authObj) + public override CoordinateSystem VisitWkt(WktCrsParser.WktContext context) { - result.AuthorityCode = authObj.Code; - result.Authority = authObj.Name; - } + var projcsCtx = context.projcs(); + if (projcsCtx != null) + { + var visitor = ProjectedCoordinateSystemVisitor.Instance; + return visitor.VisitProjcs(projcsCtx); + } + + var gcsCtx = context.geogcs(); + if (gcsCtx != null) + { + var visitor = GeographicCoordinateSystemVisitor.Instance; + return visitor.VisitGeogcs(gcsCtx); + } - return result; + var ccsCtx = context.geoccs(); + if (ccsCtx != null) + { + var visitor = GeocentricCoordinateSystemVisitor.Instance; + return visitor.VisitGeoccs(ccsCtx); + } + + //if (context.compdcs() != null) + //return (CoordinateSystem) Visit(context.compdcs()); + var fcsCtx = context.fittedcs(); + if (fcsCtx != null) + { + var visitor = FittedCoordinateSystemVisitor.Instance; + return visitor.VisitFittedcs(fcsCtx); + } + /* + else if (context.localcs()!=null) + return (CoordinateSystem) Visit(context.localcs()); + else if (context.vertcs()!=null) + return (CoordinateSystem) Visit(context.vertcs()); + */ + + return base.VisitWkt(context); + } } - public override object VisitProjcs(WktCrsParser.ProjcsContext context) - { - string name = string.Empty; - if (context.name() != null) - name = (string) Visit(context.name()); + private static WktCrsLexer cachedLexer = null; + private static WktCrsParser cachedParser = null; - GeographicCoordinateSystem gcs = null; - if (context.geogcs() != null) - gcs = (GeographicCoordinateSystem) Visit(context.geogcs()); - var lu = (LinearUnit) null; - if (context.unit() != null) + /// + /// ParseAndBuild + /// + /// + /// + public static CoordinateSystem ParseAndBuild(string input) + { + try { - var u = (ProjNet.CoordinateSystems.Unit) Visit(context.unit()); - if (u != null) + var stream = CharStreams.fromString(input); + var lexer = cachedLexer; + if (lexer == null) { - lu = new LinearUnit(u.ConversionFactor, u.Name, u.Authority, u.AuthorityCode, string.Empty, - string.Empty, string.Empty); + lexer = new WktCrsLexer(stream); + cachedLexer = lexer; + } + else + { + lexer.SetInputStream(stream); } - } - Projection p = null; - if (context.projection() != null) - p = (Projection) Visit(context.projection()); + var tokens = new CommonTokenStream(lexer); + var parser = cachedParser; + if (parser == null) + { + parser = new WktCrsParser(tokens); + parser.BuildParseTree = true; + cachedParser = parser; + } + else + { + //parser.BuildParseTree = false; + parser.TokenStream = tokens; + } - var axisCtx = context.axis(); - var ax1 = axisCtx.Length > 0 ? (AxisInfo) Visit(axisCtx[0]) : new AxisInfo("X", AxisOrientationEnum.East); - var ax2 = axisCtx.Length > 1 ? (AxisInfo) Visit(axisCtx[1]) : new AxisInfo("Y", AxisOrientationEnum.North); - var aa = new List {ax1, ax2}; + parser.Interpreter.PredictionMode = Antlr4.Runtime.Atn.PredictionMode.SLL; - Authority authority = null; - if (context.authority() != null) - { - authority = (Authority) Visit(context.authority()); - } + bool doProfile = false; + parser.Profile = doProfile; + + var wktCtx = parser.wkt(); + if (wktCtx != null) + { + var result = WktCrsVisitor.Instance.VisitWkt(wktCtx); - var result = new ProjectedCoordinateSystem(gcs.HorizontalDatum, gcs, lu, p, - aa, name, authority?.Name, authority!=null?authority.Code:-1, string.Empty, string.Empty, string.Empty); + if (doProfile) + { + Console.WriteLine("Profile results: \n" + GetProfileInfo(parser)); + } - return result; + return result; + } + } + catch (RecognitionException) + { + return null; + } + catch (ParseCanceledException) + { + return null; + } + return null; } - public WktCrsParser.WktContext ParseAndBuild(string input) - { - var stream = CharStreams.fromString(input); - var lexer = new WktCrsLexer(stream); - var tokens = new CommonTokenStream(lexer); - var parser = new WktCrsParser(tokens); - var wktContext = parser.wkt(); - this.Visit(wktContext); + private static string GetProfileInfo(WktCrsParser parser) + { + var sb = new StringBuilder(); + sb.AppendFormat("{0,-35}", "rule"); + sb.AppendFormat("{0,-15}", "time"); + sb.AppendFormat("{0,-15}", "invocations"); + sb.AppendFormat("{0,-15}", "lookahead"); + sb.AppendFormat("{0,-15}", "lookahead(max)"); + sb.AppendFormat("{0,-15}", "ambiguities"); + sb.AppendFormat("{0,-15}", "errors"); + sb.AppendLine(); + foreach (var decisionInfo in parser.ParseInfo.getDecisionInfo()) + { + var ds = parser.Atn.GetDecisionState(decisionInfo.decision); + string rule = parser.RuleNames[ds.ruleIndex]; + if (decisionInfo.timeInPrediction > 0) + { + sb.AppendFormat("{0,-35}", rule); + sb.AppendFormat("{0,-15}", decisionInfo.timeInPrediction); + sb.AppendFormat("{0,-15}", decisionInfo.invocations); + sb.AppendFormat("{0,-15}", decisionInfo.SLL_TotalLook); + sb.AppendFormat("{0,-15}", decisionInfo.SLL_MaxLook); + sb.AppendFormat("{0,-15}", decisionInfo.ambiguities.Count); + sb.AppendFormat("{0,-15}", decisionInfo.errors.Count); + sb.AppendLine(); + } + } - return wktContext; + return sb.ToString(); } + } } diff --git a/test/ProjNet.Tests/WKT/WktToProjBuilderTests.cs b/test/ProjNet.Tests/WKT/WktToProjBuilderTests.cs index 5bc7e2f..f78cf18 100644 --- a/test/ProjNet.Tests/WKT/WktToProjBuilderTests.cs +++ b/test/ProjNet.Tests/WKT/WktToProjBuilderTests.cs @@ -1,3 +1,4 @@ +using System; using NUnit.Framework; using ProjNet.CoordinateSystems; using ProjNet.IO.Wkt; @@ -34,7 +35,9 @@ public void TestProjectedCoordinateSystem_EPSG_2918() "UNIT[\"US survey foot\", 0.304800609601219, AUTHORITY[\"EPSG\", \"9003\"]], "+ "AUTHORITY[\"EPSG\", \"2918\"]]"; - _wktBuilder.ParseAndBuild(wkt); + var cs = WktToProjBuilder.ParseAndBuild(wkt); + + Assert.IsNotNull(cs); } @@ -67,10 +70,11 @@ public void ParseAllWKTs_ANTLR() int parseCount = 0; foreach (var wkt in SRIDReader.GetSrids()) { - var cs1 = _wktBuilder.ParseAndBuild(wkt.Wkt); - Assert.IsNotNull(cs1, "Could not parse WKT: " + wkt); - var cs2 = _wktBuilder.ParseAndBuild(wkt.Wkt.Replace("[", "(").Replace("]", ")")); - //Assert.That(cs1.Equals(cs2), Is.True); + var cs1 = WktToProjBuilder.ParseAndBuild(wkt.Wkt); + Assert.IsNotNull(cs1, "Could not parse WKT: " + wkt.Wkt); + var cs2 = WktToProjBuilder.ParseAndBuild(wkt.Wkt.Replace("[", "(").Replace("]", ")")); + Assert.IsNotNull(cs2, "Could not parse WKT: " + wkt.Wkt); + Assert.That(cs1.EqualParams(cs2), Is.True); parseCount++; } Assert.That(parseCount, Is.GreaterThan(2671), "Not all WKT was parsed"); @@ -98,8 +102,84 @@ public void ParseProjCSWithExtension() "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\"]," + "AUTHORITY[\"EPSG\",\"900913\"]]"; - var cs1 = _wktBuilder.ParseAndBuild(wkt); + var cs1 = WktToProjBuilder.ParseAndBuild(wkt); + + Assert.IsNotNull(cs1); + } + + + [Test] + public void ParseProjCSWithoutExtension() + { + string wkt = "PROJCS[\"Google Maps Global Mercator\"," + + "GEOGCS[\"WGS 84\"," + + "DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]]," + + "AUTHORITY[\"EPSG\",\"6326\"]]," + + "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]]," + + "UNIT[\"degree\",0.01745329251994328,AUTHORITY[\"EPSG\",\"9122\"]]," + + "AUTHORITY[\"EPSG\",\"4326\"]]," + + "PROJECTION[\"Mercator_2SP\"]," + + "PARAMETER[\"standard_parallel_1\",0]," + + "PARAMETER[\"latitude_of_origin\",0]," + + "PARAMETER[\"central_meridian\",0]," + + "PARAMETER[\"false_easting\",0]," + + "PARAMETER[\"false_northing\",0]," + + "UNIT[\"Meter\",1]," + + "AUTHORITY[\"EPSG\",\"900913\"]]"; + + var cs1 = WktToProjBuilder.ParseAndBuild(wkt); Assert.IsNotNull(cs1); } + + + [Test] + 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]]," + + "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\"]]" + + "]"; + + + try + { + //fcs = fac.CreateFromWkt (wkt) as FittedCoordinateSystem; + fcs = WktToProjBuilder.ParseAndBuild(wkt) as FittedCoordinateSystem; + } + catch (Exception ex) + { + Assert.Fail ("Could not create fitted coordinate system from:\r\n" + wkt + "\r\n" + ex.Message); + } + + Assert.That(fcs, Is.Not.Null); + Assert.That(fcs.ToBase(), Is.Not.Null.Or.Empty); + Assert.That(fcs.BaseCoordinateSystem, Is.Not.Null); + + Assert.AreEqual ("Local coordinate system MNAU (based on Gauss-Krueger)", fcs.Name); + //Assert.AreEqual ("CUSTOM", fcs.Authority); + //Assert.AreEqual (123456, fcs.AuthorityCode); + + Assert.AreEqual ("EPSG", fcs.BaseCoordinateSystem.Authority); + Assert.AreEqual (31467, fcs.BaseCoordinateSystem.AuthorityCode); + } }