Skip to content

Commit ac162b5

Browse files
committed
WKT2: Complete parsing/model/serialization implemented
New object-oriented WKT2 CRS model introduced (including for GEOGCRS, PROJCRS, VERTCRS, COMPOUNDCRS, BOUNDCRS, ENGCRS, PARAMETRICCRS) with all components. The WKT2 reader has been fundamentally implemented and now supports parsing into the new model as well as conversion to/from ProjNet objects. A new writer enables serialization back to WKT2. Extensive unit tests for parsing, roundtrip, and model structure added. This enables complete, roundtrip-capable WKT2 parsing and serialization in ProjNet for the first time. References #83 and #86
1 parent 94079f1 commit ac162b5

36 files changed

Lines changed: 3629 additions & 130 deletions
Lines changed: 130 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,35 @@
1-
using ProjNet.CoordinateSystems.Transformations;
2-
using System;
3-
using System.Collections.Generic;
4-
5-
namespace ProjNet.CoordinateSystems.Projections
6-
{
7-
/// <summary>
8-
/// Registry class for all known <see cref="MapProjection"/>s.
9-
/// </summary>
10-
public class ProjectionsRegistry
11-
{
12-
private static readonly Dictionary<string, Type> TypeRegistry = new Dictionary<string, Type>();
13-
private static readonly Dictionary<string, Type> ConstructorRegistry = new Dictionary<string, Type>();
14-
15-
private static readonly object RegistryLock = new object();
16-
17-
/// <summary>
18-
/// Static constructor
19-
/// </summary>
20-
static ProjectionsRegistry()
21-
{
22-
Register("mercator", typeof(Mercator));
23-
Register("mercator_1sp", typeof(Mercator));
1+
using ProjNet.CoordinateSystems.Transformations;
2+
using System;
3+
using System.Collections.Generic;
4+
5+
namespace ProjNet.CoordinateSystems.Projections
6+
{
7+
/// <summary>
8+
/// Registry class for all known <see cref="MapProjection"/>s.
9+
/// </summary>
10+
public class ProjectionsRegistry
11+
{
12+
private static readonly Dictionary<string, Type> TypeRegistry = new Dictionary<string, Type>();
13+
private static readonly Dictionary<string, Type> ConstructorRegistry = new Dictionary<string, Type>();
14+
15+
private static readonly object RegistryLock = new object();
16+
17+
/// <summary>
18+
/// Static constructor
19+
/// </summary>
20+
static ProjectionsRegistry()
21+
{
22+
Register("mercator", typeof(Mercator));
23+
Register("mercator_1sp", typeof(Mercator));
2424
Register("mercator_2sp", typeof(Mercator));
2525
Register("mercator_auxiliary_sphere", typeof(MercatorAuxiliarySphere));
26-
Register("pseudo_mercator", typeof(PseudoMercator));
27-
Register("popular_visualisation_pseudo_mercator", typeof(PseudoMercator));
26+
Register("pseudo_mercator", typeof(PseudoMercator));
27+
Register("popular_visualisation_pseudo_mercator", typeof(PseudoMercator));
2828
Register("google_mercator", typeof(PseudoMercator));
2929

30-
Register("transverse_mercator", typeof(TransverseMercator));
31-
Register("gauss_kruger", typeof(TransverseMercator));
32-
30+
Register("transverse_mercator", typeof(TransverseMercator));
31+
Register("gauss_kruger", typeof(TransverseMercator));
32+
3333
Register("albers", typeof(AlbersProjection));
3434
Register("albers_conic_equal_area", typeof(AlbersProjection));
3535

@@ -39,66 +39,66 @@ static ProjectionsRegistry()
3939

4040
Register("lambert_conformal_conic", typeof(LambertConformalConic2SP));
4141
Register("lambert_conformal_conic_2sp", typeof(LambertConformalConic2SP));
42-
Register("lambert_conic_conformal_(2sp)", typeof(LambertConformalConic2SP));
43-
Register("lambert_tangential_conformal_conic_projection", typeof(LambertConformalConic2SP));
44-
45-
Register("lambert_azimuthal_equal_area", typeof(LambertAzimuthalEqualAreaProjection));
46-
47-
Register("cassini_soldner", typeof(CassiniSoldnerProjection));
48-
Register("hotine_oblique_mercator", typeof(HotineObliqueMercatorProjection));
49-
Register("hotine_oblique_mercator_azimuth_center", typeof(HotineObliqueMercatorProjection));
50-
Register("oblique_mercator", typeof(ObliqueMercatorProjection));
51-
Register("oblique_stereographic", typeof(ObliqueStereographicProjection));
52-
Register("orthographic", typeof(OrthographicProjection));
53-
Register("polar_stereographic", typeof(PolarStereographicProjection));
54-
}
55-
56-
/// <summary>
57-
/// Method to register a new Map
58-
/// </summary>
59-
/// <param name="name"></param>
60-
/// <param name="type"></param>
61-
public static void Register(string name, Type type)
62-
{
63-
if (string.IsNullOrWhiteSpace(name))
64-
throw new ArgumentNullException(nameof(name));
65-
66-
if (type == null)
67-
throw new ArgumentNullException(nameof(type));
68-
69-
if (!typeof(MathTransform).IsAssignableFrom(type))
70-
throw new ArgumentException("The provided type does not implement 'GeoAPI.CoordinateSystems.Transformations.IMathTransform'!", nameof(type));
71-
72-
var ci = CheckConstructor(type);
73-
if (ci == null)
74-
throw new ArgumentException("The provided type is lacking a suitable constructor", nameof(type));
75-
76-
string key = ProjectionNameToRegistryKey(name);
77-
lock (RegistryLock)
78-
{
79-
if (TypeRegistry.ContainsKey(key))
80-
{
81-
var rt = TypeRegistry[key];
82-
if (ReferenceEquals(type, rt))
83-
return;
84-
throw new ArgumentException("A different projection type has been registered with this name", "name");
85-
}
86-
87-
TypeRegistry.Add(key, type);
88-
ConstructorRegistry.Add(key, ci);
89-
}
90-
}
91-
42+
Register("lambert_conic_conformal_(2sp)", typeof(LambertConformalConic2SP));
43+
Register("lambert_tangential_conformal_conic_projection", typeof(LambertConformalConic2SP));
44+
45+
Register("lambert_azimuthal_equal_area", typeof(LambertAzimuthalEqualAreaProjection));
46+
47+
Register("cassini_soldner", typeof(CassiniSoldnerProjection));
48+
Register("hotine_oblique_mercator", typeof(HotineObliqueMercatorProjection));
49+
Register("hotine_oblique_mercator_azimuth_center", typeof(HotineObliqueMercatorProjection));
50+
Register("oblique_mercator", typeof(ObliqueMercatorProjection));
51+
Register("oblique_stereographic", typeof(ObliqueStereographicProjection));
52+
Register("orthographic", typeof(OrthographicProjection));
53+
Register("polar_stereographic", typeof(PolarStereographicProjection));
54+
}
55+
56+
/// <summary>
57+
/// Method to register a new Map
58+
/// </summary>
59+
/// <param name="name"></param>
60+
/// <param name="type"></param>
61+
public static void Register(string name, Type type)
62+
{
63+
if (string.IsNullOrWhiteSpace(name))
64+
throw new ArgumentNullException(nameof(name));
65+
66+
if (type == null)
67+
throw new ArgumentNullException(nameof(type));
68+
69+
if (!typeof(MathTransform).IsAssignableFrom(type))
70+
throw new ArgumentException("The provided type does not implement 'GeoAPI.CoordinateSystems.Transformations.IMathTransform'!", nameof(type));
71+
72+
var ci = CheckConstructor(type);
73+
if (ci == null)
74+
throw new ArgumentException("The provided type is lacking a suitable constructor", nameof(type));
75+
76+
string key = ProjectionNameToRegistryKey(name);
77+
lock (RegistryLock)
78+
{
79+
if (TypeRegistry.ContainsKey(key))
80+
{
81+
var rt = TypeRegistry[key];
82+
if (ReferenceEquals(type, rt))
83+
return;
84+
throw new ArgumentException("A different projection type has been registered with this name", "name");
85+
}
86+
87+
TypeRegistry.Add(key, type);
88+
ConstructorRegistry.Add(key, ci);
89+
}
90+
}
91+
9292
private static string ProjectionNameToRegistryKey(string name)
9393
{
9494
return name.ToLowerInvariant().Replace(' ', '_').Replace("-", "_");
95-
}
96-
95+
}
96+
9797
/// <summary>
9898
/// Register an alias for an existing Map.
9999
/// </summary>
100100
/// <param name="aliasName"></param>
101-
/// <param name="existingName"></param>
101+
/// <param name="existingName"></param>
102102
public static void RegisterAlias(string aliasName, string existingName)
103103
{
104104
lock (RegistryLock)
@@ -110,52 +110,52 @@ public static void RegisterAlias(string aliasName, string existingName)
110110

111111
Register(aliasName, existingProjectionType);
112112
}
113-
}
114-
115-
private static Type CheckConstructor(Type type)
116-
{
117-
// find a constructor that accepts exactly one parameter that's an
118-
// instance of List<ProjectionParameter>, and then return the exact
119-
// parameter type so that we can create instances of this type with
120-
// minimal copying in the future, when possible.
121-
foreach (var c in type.GetConstructors())
122-
{
123-
var parameters = c.GetParameters();
124-
if (parameters.Length == 1 && parameters[0].ParameterType.IsAssignableFrom(typeof(List<ProjectionParameter>)))
125-
{
126-
return parameters[0].ParameterType;
127-
}
128-
}
129-
130-
return null;
131-
}
132-
133-
internal static MathTransform CreateProjection(string className, IEnumerable<ProjectionParameter> parameters)
134-
{
135-
string key = ProjectionNameToRegistryKey(className);
136-
137-
Type projectionType;
138-
Type ci;
139-
140-
lock (RegistryLock)
141-
{
142-
if (!TypeRegistry.TryGetValue(key, out projectionType))
143-
throw new NotSupportedException($"Projection {className} is not supported.");
144-
ci = ConstructorRegistry[key];
145-
}
146-
147-
if (!ci.IsInstanceOfType(parameters))
148-
{
149-
parameters = new List<ProjectionParameter>(parameters);
150-
}
151-
152-
var res = (MapProjection)Activator.CreateInstance(projectionType, parameters);
153-
if (!res.Name.Equals(className, StringComparison.InvariantCultureIgnoreCase))
154-
{
155-
res.Alias = res.Name;
156-
res.Name = className;
157-
}
158-
return res;
159-
}
160-
}
161-
}
113+
}
114+
115+
private static Type CheckConstructor(Type type)
116+
{
117+
// find a constructor that accepts exactly one parameter that's an
118+
// instance of List<ProjectionParameter>, and then return the exact
119+
// parameter type so that we can create instances of this type with
120+
// minimal copying in the future, when possible.
121+
foreach (var c in type.GetConstructors())
122+
{
123+
var parameters = c.GetParameters();
124+
if (parameters.Length == 1 && parameters[0].ParameterType.IsAssignableFrom(typeof(List<ProjectionParameter>)))
125+
{
126+
return parameters[0].ParameterType;
127+
}
128+
}
129+
130+
return null;
131+
}
132+
133+
internal static MathTransform CreateProjection(string className, IEnumerable<ProjectionParameter> parameters)
134+
{
135+
string key = ProjectionNameToRegistryKey(className);
136+
137+
Type projectionType;
138+
Type ci;
139+
140+
lock (RegistryLock)
141+
{
142+
if (!TypeRegistry.TryGetValue(key, out projectionType))
143+
throw new NotSupportedException($"Projection {className} is not supported.");
144+
ci = ConstructorRegistry[key];
145+
}
146+
147+
if (!ci.IsInstanceOfType(parameters))
148+
{
149+
parameters = new List<ProjectionParameter>(parameters);
150+
}
151+
152+
var res = (MapProjection)Activator.CreateInstance(projectionType, parameters);
153+
if (!res.Name.Equals(className, StringComparison.InvariantCultureIgnoreCase))
154+
{
155+
res.Alias = res.Name;
156+
res.Name = className;
157+
}
158+
return res;
159+
}
160+
}
161+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace ProjNet.CoordinateSystems.Wkt2
5+
{
6+
/// <summary>
7+
/// Abridged transformation as used in WKT2 <c>BOUNDCRS</c>.
8+
/// </summary>
9+
[Serializable]
10+
public sealed class Wkt2AbridgedTransformation
11+
{
12+
/// <summary>
13+
/// Initializes a new instance.
14+
/// </summary>
15+
/// <param name="name">Transformation name.</param>
16+
/// <param name="methodName">Transformation method name.</param>
17+
public Wkt2AbridgedTransformation(string name, string methodName)
18+
{
19+
Name = name ?? throw new ArgumentNullException(nameof(name));
20+
MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName));
21+
}
22+
23+
public string Name { get; }
24+
25+
public string MethodName { get; }
26+
27+
public List<Wkt2Parameter> Parameters { get; } = new List<Wkt2Parameter>();
28+
29+
public Wkt2Id Id { get; set; }
30+
31+
public string Remark { get; set; }
32+
}
33+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System;
2+
3+
namespace ProjNet.CoordinateSystems.Wkt2
4+
{
5+
/// <summary>
6+
/// Axis element as used in WKT2.
7+
/// </summary>
8+
[Serializable]
9+
public sealed class Wkt2Axis
10+
{
11+
/// <summary>
12+
/// Initializes a new instance.
13+
/// </summary>
14+
/// <param name="name">Axis name.</param>
15+
/// <param name="direction">Axis direction (e.g. <c>north</c>, <c>east</c>).</param>
16+
public Wkt2Axis(string name, string direction)
17+
{
18+
Name = name ?? throw new ArgumentNullException(nameof(name));
19+
Direction = direction ?? throw new ArgumentNullException(nameof(direction));
20+
}
21+
22+
/// <summary>
23+
/// Gets the axis name.
24+
/// </summary>
25+
public string Name { get; }
26+
27+
/// <summary>
28+
/// Gets the axis direction.
29+
/// </summary>
30+
public string Direction { get; }
31+
32+
/// <summary>
33+
/// Gets or sets the axis order.
34+
/// </summary>
35+
public int? Order { get; set; }
36+
37+
/// <summary>
38+
/// Gets or sets an optional axis unit.
39+
/// </summary>
40+
public Wkt2Unit Unit { get; set; }
41+
42+
/// <summary>
43+
/// Gets or sets an optional identifier.
44+
/// </summary>
45+
public Wkt2Id Id { get; set; }
46+
}
47+
}

0 commit comments

Comments
 (0)