-
Notifications
You must be signed in to change notification settings - Fork 310
/
LargestEmptyCircle.cs
381 lines (337 loc) · 14.9 KB
/
LargestEmptyCircle.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
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
using System;
using NetTopologySuite.Algorithm.Locate;
using NetTopologySuite.Geometries;
using NetTopologySuite.Operation.Distance;
using NetTopologySuite.Utilities;
namespace NetTopologySuite.Algorithm.Construct
{
/// <summary>
/// Constructs the Largest Empty Circle for a set
/// of obstacle geometries, up to a specified tolerance.
/// The obstacles are point and line geometries.
/// <para/>
/// The Largest Empty Circle is the largest circle which
/// has its center in the convex hull of the obstacles (the <i>boundary</i>),
/// and whose interior does not intersect with any obstacle.
/// The circle center is the point in the interior of the boundary
/// which has the farthest distance from the obstacles (up to tolerance).
/// The circle is determined by the center point
/// and a point lying on an obstacle indicating the circle radius.
/// <para/>
/// The implementation uses a successive-approximation technique
/// over a grid of square cells covering the obstacles and boundary.
/// The grid is refined using a branch-and-bound algorithm.
/// Point containment and distance are computed in a performant
/// way by using spatial indexes.
/// <para/>
/// <h3>Future Enhancements</h3>
/// <list type="bullet">
/// <item><description>Support polygons as obstacles</description></item>
/// <item><description>Support a client-defined boundary polygon</description></item>
/// </list>
/// </summary>
/// <author>Martin Davis</author>
/// <see cref="MaximumInscribedCircle"/>
/// <see cref="InteriorPoint"/>
/// <see cref="Centroid"/>
public class LargestEmptyCircle
{
/// <summary>
/// Computes the center point of the Largest Empty Circle
/// within a set of obstacles, up to a given tolerance distance.
/// </summary>
/// <param name="obstacles">A geometry representing the obstacles (points and lines)</param>
/// <param name="tolerance">The distance tolerance for computing the center point</param>
/// <returns>The center point of the Largest Empty Circle</returns>
public static Point GetCenter(Geometry obstacles, double tolerance)
{
var lec = new LargestEmptyCircle(obstacles, tolerance);
return lec.GetCenter();
}
/// <summary>
/// Computes a radius line of the Largest Empty Circle
/// within a set of obstacles, up to a given distance tolerance.
/// </summary>
/// <param name="obstacles">A geometry representing the obstacles (points and lines)</param>
/// <param name="tolerance">The distance tolerance for computing the center point</param>
/// <returns>A line from the center of the circle to a point on the edge</returns>
public static LineString GetRadiusLine(Geometry obstacles, double tolerance)
{
var lec = new LargestEmptyCircle(obstacles, tolerance);
return lec.GetRadiusLine();
}
private readonly Geometry _obstacles;
private readonly double _tolerance;
private readonly GeometryFactory _factory;
private Geometry _boundary;
private IndexedPointInAreaLocator _ptLocater;
private readonly IndexedFacetDistance _obstacleDistance;
private IndexedFacetDistance _boundaryDistance;
private Cell _farthestCell;
private Cell _centerCell;
private Coordinate _centerPt;
private Point _centerPoint;
private Coordinate _radiusPt;
private Point _radiusPoint;
/// <summary>
/// Creates a new instance of a Largest Empty Circle construction.
/// </summary>
/// <param name="obstacles">A geometry representing the obstacles (points and lines)</param>
/// <param name="tolerance">The distance tolerance for computing the center point</param>
public LargestEmptyCircle(Geometry obstacles, double tolerance)
{
if (obstacles.IsEmpty)
{
throw new ArgumentException("Empty obstacles geometry is not supported");
}
_obstacles = obstacles;
_factory = obstacles.Factory;
_tolerance = tolerance;
_obstacleDistance = new IndexedFacetDistance(obstacles);
SetBoundary(obstacles);
}
/// <summary>
/// Sets the area boundary as the convex hull
/// of the obstacles.
/// </summary>
private void SetBoundary(Geometry obstacles)
{
// TODO: allow this to be set by client as arbitrary polygon
this._boundary = obstacles.ConvexHull();
// if boundary does not enclose an area cannot create a ptLocater
if (_boundary.Dimension >= Dimension.Surface)
{
_ptLocater = new IndexedPointInAreaLocator(_boundary);
_boundaryDistance = new IndexedFacetDistance(_boundary);
}
}
/// <summary>
/// Gets the center point of the Largest Empty Circle
/// (up to the tolerance distance).
/// </summary>
/// <returns>The center point of the Largest Empty Circle</returns>
public Point GetCenter()
{
Compute();
return _centerPoint;
}
/// <summary>
/// Gets a point defining the radius of the Largest Empty Circle.
/// This is a point on the obstacles which is
/// nearest to the computed center of the Largest Empty Circle.
/// The line segment from the center to this point
/// is a radius of the constructed circle, and this point
/// lies on the boundary of the circle.
/// </summary>
/// <returns>A point defining the radius of the Largest Empty Circle</returns>
public Point GetRadiusPoint()
{
Compute();
return _radiusPoint;
}
/// <summary>
/// Gets a line representing a radius of the Largest Empty Circle.
/// </summary>
/// <returns>A line from the center of the circle to a point on the edge</returns>
public LineString GetRadiusLine()
{
Compute();
var radiusLine = _factory.CreateLineString(new[] {_centerPt.Copy(), _radiusPt.Copy()});
return radiusLine;
}
/// <summary>
/// Computes the signed distance from a point to the constraints
/// (obstacles and boundary).
/// Points outside the boundary polygon are assigned a negative distance.
/// Their containing cells will be last in the priority queue
/// (but will still end up being tested since they may be refined).
/// </summary>
/// <param name="p">The point to compute the distance for</param>
/// <returns>The signed distance to the constraints (negative indicates outside the boundary)</returns>
private double DistanceToConstraints(Point p)
{
bool isOutide = Location.Exterior == _ptLocater.Locate(p.Coordinate);
if (isOutide)
{
double boundaryDist = _boundaryDistance.Distance(p);
return -boundaryDist;
}
double dist = _obstacleDistance.Distance(p);
return dist;
}
private double DistanceToConstraints(double x, double y)
{
var coord = new Coordinate(x, y);
var pt = _factory.CreatePoint(coord);
return DistanceToConstraints(pt);
}
private void Compute()
{
// check if already computed
if (_centerCell != null) return;
// if ptLocater is not present then result is degenerate (represented as zero-radius circle)
if (_ptLocater == null)
{
var pt = _obstacles.Coordinate;
_centerPt = pt.Copy();
_centerPoint = _factory.CreatePoint(pt);
_radiusPt = pt.Copy();
_radiusPoint = _factory.CreatePoint(pt);
return;
}
// Priority queue of cells, ordered by decreasing distance from constraints
var cellQueue = new PriorityQueue<Cell>();
CreateInitialGrid(_obstacles.EnvelopeInternal, cellQueue);
// use the area centroid as the initial candidate center point
_farthestCell = CreateCentroidCell(_obstacles);
//int totalCells = cellQueue.size();
/*
* Carry out the branch-and-bound search
* of the cell space
*/
while (!cellQueue.IsEmpty())
{
// pick the cell with greatest distance from the queue
var cell = cellQueue.Poll();
// update the center cell if the candidate is further from the constraints
if (cell.Distance > _farthestCell.Distance)
{
_farthestCell = cell;
}
/*
* If this cell may contain a better approximation to the center
* of the empty circle, then refine it (partition into subcells
* which are added into the queue for further processing).
* Otherwise the cell is pruned (not investigated further),
* since no point in it can be further than the current farthest distance.
*/
if (MayContainCircleCenter(cell))
{
// split the cell into four sub-cells
double h2 = cell.HSide / 2;
cellQueue.Add(CreateCell(cell.X - h2, cell.Y - h2, h2));
cellQueue.Add(CreateCell(cell.X + h2, cell.Y - h2, h2));
cellQueue.Add(CreateCell(cell.X - h2, cell.Y + h2, h2));
cellQueue.Add(CreateCell(cell.X + h2, cell.Y + h2, h2));
//totalCells += 4;
}
}
// the farthest cell is the best approximation to the LEC center
_centerCell = _farthestCell;
// compute center point
_centerPt = new Coordinate(_centerCell.X, _centerCell.Y);
_centerPoint = _factory.CreatePoint(_centerPt);
// compute radius point
var nearestPts = _obstacleDistance.NearestPoints(_centerPoint);
_radiusPt = nearestPts[0].Copy();
_radiusPoint = _factory.CreatePoint(_radiusPt);
}
/// <summary>
/// Tests whether a cell may contain the circle center,
/// and thus should be refined (split into subcells
/// to be investigated further.)
/// </summary>
/// <param name="cell">The cell to test</param>
/// <returns><c>true</c> if the cell might contain the circle center</returns>
private bool MayContainCircleCenter(Cell cell)
{
/*
* Every point in the cell lies outside the boundary,
* so they cannot be the center point
*/
if (cell.IsFullyOutside)
return false;
/*
* The cell is outside, but overlaps the boundary
* so it may contain a point which should be checked.
* This is only the case if the potential overlap distance
* is larger than the tolerance.
*/
if (cell.IsOutside)
{
bool isOverlapSignificant = cell.MaxDistance > _tolerance;
return isOverlapSignificant;
}
/*
* Cell is inside the boundary. It may contain the center
* if the maximum possible distance is greater than the current distance
* (up to tolerance).
*/
double potentialIncrease = cell.MaxDistance - _farthestCell.Distance;
return potentialIncrease > _tolerance;
}
/// <summary>
/// Initializes the queue with a grid of cells covering
/// the extent of the area.
/// </summary>
/// <param name="env">The area extent to cover</param>
/// <param name="cellQueue">The queue to initialize</param>
private void CreateInitialGrid(Envelope env, PriorityQueue<Cell> cellQueue)
{
double minX = env.MinX;
double maxX = env.MaxX;
double minY = env.MinY;
double maxY = env.MaxY;
double width = env.Width;
double height = env.Height;
double cellSize = Math.Min(width, height);
double hSize = cellSize / 2.0;
// compute initial grid of cells to cover area
for (double x = minX; x < maxX; x += cellSize)
{
for (double y = minY; y < maxY; y += cellSize)
{
cellQueue.Add(CreateCell(x + hSize, y + hSize, hSize));
}
}
}
private Cell CreateCell(double x, double y, double h)
{
return new Cell(x, y, h, DistanceToConstraints(x, y));
}
// create a cell centered on area centroid
private Cell CreateCentroidCell(Geometry geom)
{
var p = geom.Centroid;
return new Cell(p.X, p.Y, 0, DistanceToConstraints(p));
}
/// <summary>
/// A square grid cell centered on a given point
/// with a given side half-length,
/// and having a given distance from the center point to the constraints.
/// The maximum possible distance from any point in the cell to the
/// constraints can be computed.
/// This is used as the ordering and upper-bound function in
/// the branch-and-bound algorithm.
/// </summary>
private class Cell : IComparable<Cell>
{
private const double Sqrt2 = 1.4142135623730951;
public Cell(double x, double y, double hSide, double distanceToConstraints)
{
X = x; // cell center x
Y = y; // cell center y
HSide = hSide; // half the cell size
// the distance from cell center to constraints
Distance = distanceToConstraints;
/*
* The maximum possible distance to the constraints for points in this cell
* is the center distance plus the radius (half the diagonal length).
*/
MaxDistance = Distance + hSide * Sqrt2;
}
public bool IsFullyOutside => MaxDistance < 0;
public bool IsOutside => Distance < 0;
public double MaxDistance { get; }
public double Distance { get; }
public double HSide { get; }
public double X { get; }
public double Y { get; }
public int CompareTo(Cell o)
{
// A cell is greater if its maximum distance is larger.
return (int) (o.MaxDistance - this.MaxDistance);
}
}
}
}