/
GeometryFactoryEx.cs
237 lines (211 loc) · 10.6 KB
/
GeometryFactoryEx.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
using System;
using NetTopologySuite.Geometries.Implementation;
namespace NetTopologySuite.Geometries
{
/// <summary>
/// An extended <see cref="GeometryFactory"/> that is capable of enforcing a ring orientation for polygons.
/// </summary>
public class GeometryFactoryEx : GeometryFactory
{
/// <summary>
/// Gets or sets the default polygon shell ring orientation that is used when nothing else has been set.
/// </summary>
private static LinearRingOrientation DefaultShellRingOrientation { get; set; } = LinearRingOrientation.Default;
private static readonly PrecisionModel FloatingPrecisionModel = new PrecisionModel();
private static CoordinateSequenceFactory _defaultCoordinateSequenceFactory;
private static PrecisionModel _defaultPrecisionModel;
/// <summary>
/// Gets or sets the default precision model to use with these geometry factories
/// </summary>
public static PrecisionModel DefaultPrecisionModel
{
get => _defaultPrecisionModel ?? FloatingPrecisionModel;
set => _defaultPrecisionModel = value;
}
/// <summary>
/// Gets or sets the default coordinate sequence factory to use with these geometry factories
/// </summary>
public static CoordinateSequenceFactory DefaultCoordinateSequenceFactory
{
get => _defaultCoordinateSequenceFactory ?? CoordinateArraySequenceFactory.Instance;
set => _defaultCoordinateSequenceFactory = value;
}
/// <summary>
/// Gets or sets the default spatial reference id.
/// </summary>
public static int DefaultSRID { get; set; } = 0;
/// <summary>
/// Constructs a GeometryFactory that generates Geometries having the given
/// PrecisionModel, spatial-reference ID, and CoordinateSequence implementation.
/// </summary>
public GeometryFactoryEx(PrecisionModel precisionModel, int srid, CoordinateSequenceFactory coordinateSequenceFactory, NtsGeometryServices services)
: base(precisionModel, srid, coordinateSequenceFactory, services)
{ }
/// <summary>
/// Constructs a GeometryFactory that generates Geometries having the given
/// PrecisionModel, spatial-reference ID, and CoordinateSequence implementation.
/// </summary>
public GeometryFactoryEx(PrecisionModel precisionModel, int srid, CoordinateSequenceFactory coordinateSequenceFactory)
: base(precisionModel, srid, coordinateSequenceFactory)
{ }
/// <summary>
/// Constructs a GeometryFactory that generates Geometries having the given
/// CoordinateSequence implementation, a double-precision floating PrecisionModel and a
/// spatial-reference ID of 0.
/// </summary>
public GeometryFactoryEx(CoordinateSequenceFactory coordinateSequenceFactory) :
this(DefaultPrecisionModel, DefaultSRID, coordinateSequenceFactory)
{ }
/// <summary>
/// Constructs a GeometryFactory that generates Geometries having the given
/// {PrecisionModel} and the default CoordinateSequence
/// implementation.
/// </summary>
/// <param name="precisionModel">The PrecisionModel to use.</param>
public GeometryFactoryEx(PrecisionModel precisionModel) :
this(precisionModel, DefaultSRID, DefaultCoordinateSequenceFactory)
{ }
/// <summary>
/// Constructs a GeometryFactory that generates Geometries having the given
/// <c>PrecisionModel</c> and spatial-reference ID, and the default CoordinateSequence
/// implementation.
/// </summary>
/// <param name="precisionModel">The PrecisionModel to use.</param>
/// <param name="srid">The SRID to use.</param>
public GeometryFactoryEx(PrecisionModel precisionModel, int srid) :
this(precisionModel, srid, DefaultCoordinateSequenceFactory)
{ }
/// <summary>
/// Constructs a GeometryFactory that generates Geometries having a floating
/// PrecisionModel and a spatial-reference ID of 0.
/// </summary>
public GeometryFactoryEx() : this(DefaultPrecisionModel, DefaultSRID) { }
/// <summary>
/// The polygon shell ring orientation enforced by this factory
/// </summary>
private Lazy<LinearRingOrientation> _polygonShellRingOrientation =
new Lazy<LinearRingOrientation>(() => DefaultShellRingOrientation);
/// <summary>
/// Gets or sets a value indicating the ring orientation of the
/// <c>Polygon</c>'s exterior rings.<para>
/// If its value is <see cref="LinearRingOrientation.DontCare"/>, this
/// factory behaves just like the base <see cref="GeometryFactory"/>.
/// </para>
/// </summary>
/// <remarks>
/// The setter of this property has to be used prior to any call
/// to <c>CreatePolygon</c>, <c>CreateMultiPolygon</c>, or
/// <c>ReplaceSRID</c></remarks>
/// <seealso cref="GeometryFactory.CreatePolygon(Coordinate[])"/>
/// <seealso cref="GeometryFactory.CreatePolygon(CoordinateSequence)"/>
/// <seealso cref="GeometryFactory.CreatePolygon(LinearRing)"/>
/// <seealso cref="GeometryFactory.CreatePolygon(LinearRing, LinearRing[])"/>
/// <seealso cref="GeometryFactory.CreateMultiPolygon(Polygon[])"/>
public LinearRingOrientation OrientationOfExteriorRing
{
get => _polygonShellRingOrientation.Value;
set
{
if (_polygonShellRingOrientation.IsValueCreated && _polygonShellRingOrientation.Value != value)
throw new InvalidOperationException("This value is only allowed to be set before anything observes it.");
if ((int)value < -1 || (int)value > 1)
throw new ArgumentOutOfRangeException();
_polygonShellRingOrientation = new Lazy<LinearRingOrientation>(() => value);
}
}
/// <summary>
/// Gets a value indicating the ring orientation for the interior rings
/// </summary>
/// <remarks>
/// This value is always opposite of <see cref="OrientationOfExteriorRing"/>,
/// except when its value is <see cref="LinearRingOrientation.DontCare"/>.
/// </remarks>
private LinearRingOrientation OrientationOfInteriorRings
{
get
{
switch (OrientationOfExteriorRing)
{
case LinearRingOrientation.CCW:
return LinearRingOrientation.CW;
case LinearRingOrientation.CW:
return LinearRingOrientation.CCW;
default:
return LinearRingOrientation.DontCare;
}
}
}
/// <summary>
/// Constructs a <c>Polygon</c> with the given exterior boundary and
/// interior boundaries.
/// <para/>
/// The <see cref="OrientationOfExteriorRing"/> is enforced on the constructed polygon.
/// </summary>
/// <param name="shell">
/// The outer boundary of the new <c>Polygon</c>, or
/// <c>null</c> or an empty <c>LinearRing</c> if
/// the empty point is to be created.
/// </param>
/// <param name="holes">
/// The inner boundaries of the new <c>Polygon</c>, or
/// <c>null</c> or empty <c>LinearRing</c> s if
/// the empty point is to be created.
/// </param>
/// <returns>A <see cref="Polygon"/> object</returns>
public override Polygon CreatePolygon(LinearRing shell, LinearRing[] holes)
{
shell = EnsureOrientation(shell, OrientationOfExteriorRing);
if (holes != null && holes.Length > 0)
{
var ringOrientation = OrientationOfInteriorRings;
for (int i = 0; i < holes.Length; i++)
holes[i] = EnsureOrientation(holes[i], ringOrientation);
}
return new Polygon(shell, holes, this);
}
/// <summary>
/// Creates a <c>MultiPolygon</c> using the given <c>Polygons</c>; a null or empty array
/// will create an empty Polygon. The polygons must conform to the
/// assertions specified in the <see href="http://www.opengis.org/techno/specs.htm"/> OpenGIS Simple Features
/// Specification for SQL.<para/>
/// The <see cref="OrientationOfExteriorRing"/> is enforced on each polygon.
/// </summary>
/// <param name="polygons">Polygons, each of which may be empty but not null.</param>
/// <returns>A <see cref="MultiPolygon"/> object</returns>
public override MultiPolygon CreateMultiPolygon(Polygon[] polygons)
{
if (polygons == null || polygons.Length == 0)
return CreateMultiPolygon();
// Check if polygons were created with a ring orientation enforcing factory and that the ring
// orientation matches that of this one.
if (polygons[0]?.Factory is GeometryFactoryEx gfe && gfe.OrientationOfExteriorRing == OrientationOfExteriorRing)
return new MultiPolygon(polygons, this);
for (int i = 0; i < polygons.Length; i++)
polygons[i] = CreatePolygon(polygons[i].Shell, polygons[i].Holes);
return new MultiPolygon(polygons, this);
}
/// <inheritdoc />
public override GeometryFactory WithSRID(int srid)
{
var clone = new GeometryFactoryEx(PrecisionModel, srid, CoordinateSequenceFactory, GeometryServices);
// ensure that the value is initialized, to minimize the confusion when this is copied.
_ = _polygonShellRingOrientation.Value;
clone._polygonShellRingOrientation = _polygonShellRingOrientation;
return clone;
}
/// <summary>
/// Utility function to enforce a specific ring orientation on a linear ring
/// </summary>
/// <param name="ring">The ring</param>
/// <param name="ringOrientation">The required orientation</param>
/// <returns>A ring</returns>
private static LinearRing EnsureOrientation(LinearRing ring, LinearRingOrientation ringOrientation)
{
if (ring == null || ring.IsEmpty || ringOrientation == LinearRingOrientation.DontCare)
return ring;
if (ringOrientation == LinearRingOrientation.CCW != ring.IsCCW)
ring = (LinearRing)ring.Reverse();
return ring;
}
}
}