Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
GeoPositionCollection.cs
1using System;
4using System.Text.RegularExpressions;
6
7namespace Waher.Runtime.Geo
8{
12 public class GeoPositionCollection<T> : ICollection<T>
14 {
15 private readonly Dictionary<string, GeoNode> nodesById = new Dictionary<string, GeoNode>();
16 private readonly object synchObj = new object();
17 private volatile int token = 0;
18 private int count = 0;
19 private GeoNode root = null;
20
21 private class GeoNode
22 {
23 public GeoNode(T Object)
24 {
25 this.Object = Object;
26 }
27
28 public T Object;
29 public GeoNode NW;
30 public GeoNode NE;
31 public GeoNode SW;
32 public GeoNode SE;
33 public GeoNode Parent;
34 }
35
40 {
41 }
42
46 public int Count
47 {
48 get
49 {
50 lock (this.synchObj)
51 {
52 return this.count;
53 }
54 }
55 }
56
60 public bool IsReadOnly => false;
61
66 public void Add(T Object)
67 {
68 lock (this.synchObj)
69 {
70 this.AddLocked(Object);
71 }
72 }
73
74 private void AddLocked(T Object)
75 {
76 if (this.nodesById.TryGetValue(Object.GeoId, out GeoNode Node))
77 {
78 if (Node.Object.Equals(Object))
79 return;
80 else
81 throw new ArgumentException("An object with the same ID already exists in the collection.", nameof(Object));
82 }
83
84 Node = new GeoNode(Object);
85 this.nodesById[Object.GeoId] = Node;
86 this.AddNodeLocked(Node);
87 this.token++;
88 this.count++;
89 }
90
91 private void AddNodeLocked(GeoNode Node)
92 {
93 if (this.root is null)
94 this.root = Node;
95 else
96 {
97 GeoPosition Location = Node.Object.Location;
98 GeoNode Loop = this.root;
99
100 while (true)
101 {
102 if (Location.Latitude >= Loop.Object.Location.Latitude)
103 {
104 if (Location.Longitude >= Loop.Object.Location.Longitude)
105 {
106 if (Loop.NE is null)
107 {
108 Loop.NE = Node;
109 Node.Parent = Loop;
110 return;
111 }
112 else
113 Loop = Loop.NE;
114 }
115 else
116 {
117 if (Loop.NW is null)
118 {
119 Loop.NW = Node;
120 Node.Parent = Loop;
121 return;
122 }
123 else
124 Loop = Loop.NW;
125 }
126 }
127 else
128 {
129 if (Location.Longitude >= Loop.Object.Location.Longitude)
130 {
131 if (Loop.SE is null)
132 {
133 Loop.SE = Node;
134 Node.Parent = Loop;
135 return;
136 }
137 else
138 Loop = Loop.SE;
139 }
140 else
141 {
142 if (Loop.SW is null)
143 {
144 Loop.SW = Node;
145 Node.Parent = Loop;
146 return;
147 }
148 else
149 Loop = Loop.SW;
150 }
151 }
152 }
153 }
154 }
155
159 public void Clear()
160 {
161 lock (this.synchObj)
162 {
163 this.nodesById.Clear();
164 this.root = null;
165 this.count = 0;
166 this.token++;
167 }
168 }
169
175 public bool Contains(T Object)
176 {
177 lock (this.synchObj)
178 {
179 if (this.nodesById.TryGetValue(Object.GeoId, out GeoNode Node))
180 {
181 if (Node.Object.Equals(Object))
182 return true;
183 else
184 return false;
185 }
186 else
187 return false;
188 }
189 }
190
196 public bool Contains(string GeoId)
197 {
198 lock (this.synchObj)
199 {
200 return this.nodesById.ContainsKey(GeoId);
201 }
202 }
203
210 public bool TryGetObject(string GeoId, out T Object)
211 {
212 lock (this.synchObj)
213 {
214 if (!this.nodesById.TryGetValue(GeoId, out GeoNode Node))
215 {
216 Object = default;
217 return false;
218 }
219 else
220 {
221 Object = Node.Object;
222 return true;
223 }
224 }
225 }
226
232 public void CopyTo(T[] Destination, int Offset)
233 {
234 if (Destination is null)
235 throw new ArgumentNullException(nameof(Destination));
236
237 if (Offset < 0 || Offset >= Destination.Length)
238 throw new ArgumentOutOfRangeException(nameof(Offset), "Offset must be a non-negative number less than the size of the destination array.");
239
240 lock (this.synchObj)
241 {
242 if (Offset + this.nodesById.Count > Destination.Length)
243 throw new ArgumentException("Offset must allow all elements to be copied into the destination array.", nameof(Offset));
244
245 foreach (GeoNode Node in this.nodesById.Values)
246 Destination[Offset++] = Node.Object;
247 }
248 }
249
254 public T[] ToArray()
255 {
256 lock (this.synchObj)
257 {
258 T[] Result = new T[this.count];
259 int i = 0;
260
261 foreach (GeoNode Node in this.nodesById.Values)
262 Result[i++] = Node.Object;
263
264 return Result;
265 }
266 }
267
272 public IEnumerator<T> GetEnumerator()
273 {
274 lock (this.synchObj)
275 {
276 return new GeoObjectEnumerator(this, this.nodesById.Values.GetEnumerator());
277 }
278 }
279
284 IEnumerator IEnumerable.GetEnumerator()
285 {
286 return this.GetEnumerator();
287 }
288
289 private class GeoObjectEnumerator : IEnumerator<T>
290 {
291 private readonly IEnumerator<GeoNode> e;
292 private readonly GeoPositionCollection<T> collection;
293 private readonly int token;
294
295 public GeoObjectEnumerator(GeoPositionCollection<T> Collection, IEnumerator<GeoNode> Enumerator)
296 {
297 this.collection = Collection;
298 this.token = Collection.token;
299 this.e = Enumerator;
300 }
301
302 public T Current
303 {
304 get
305 {
306 lock (this.collection.synchObj)
307 {
308 if (this.token != this.collection.token)
309 throw new InvalidOperationException("Collection has been modified.");
310
311 return this.e.Current.Object;
312 }
313 }
314 }
315
316 object IEnumerator.Current => this.Current;
317
318 public void Dispose()
319 {
320 lock (this.collection.synchObj)
321 {
322 this.e.Dispose();
323 }
324 }
325
326 public bool MoveNext()
327 {
328 lock (this.collection.synchObj)
329 {
330 if (this.token != this.collection.token)
331 throw new InvalidOperationException("Collection has been modified.");
332
333 return this.e.MoveNext();
334 }
335 }
336
337 public void Reset()
338 {
339 lock (this.collection.synchObj)
340 {
341 if (this.token != this.collection.token)
342 throw new InvalidOperationException("Collection has been modified.");
343
344 this.e.Reset();
345 }
346 }
347 }
348
354 public bool Remove(T Object)
355 {
356 lock (this.synchObj)
357 {
358 return this.RemoveLocked(Object);
359 }
360 }
361
367 public bool Remove(string GeoId)
368 {
369 return this.Remove(GeoId, out _);
370 }
371
378 public bool Remove(string GeoId, out T Object)
379 {
380 lock (this.synchObj)
381 {
382 if (!this.nodesById.TryGetValue(GeoId, out GeoNode Node) ||
383 !this.RemoveLocked(Node))
384 {
385 Object = default;
386 return false;
387 }
388 else
389 {
390 Object = Node.Object;
391 return true;
392 }
393 }
394 }
395
396 private bool RemoveLocked(T Object)
397 {
398 if (!this.nodesById.TryGetValue(Object.GeoId, out GeoNode Node))
399 return false;
400
401 if (!Node.Object.Equals(Object))
402 return false;
403
404 return this.RemoveLocked(Node);
405 }
406
407 private bool RemoveLocked(GeoNode Node)
408 {
409 T Object = Node.Object;
410
411 this.nodesById.Remove(Object.GeoId);
412 this.count--;
413 this.token++;
414
415 GeoNode Parent = Node.Parent;
416 if (!(Parent is null))
417 {
418 Node.Parent = null;
419
420 if (Parent.NW == Node)
421 Parent.NW = null;
422 else if (Parent.NE == Node)
423 Parent.NE = null;
424 else if (Parent.SW == Node)
425 Parent.SW = null;
426 else if (Parent.SE == Node)
427 Parent.SE = null;
428 }
429 else
430 this.root = null;
431
432 if (!(Node.NW is null))
433 {
434 Node.NW.Parent = null;
435 this.AddNodeLocked(Node.NW);
436 Node.NW = null;
437 }
438
439 if (!(Node.NE is null))
440 {
441 Node.NE.Parent = null;
442 this.AddNodeLocked(Node.NE);
443 Node.NE = null;
444 }
445
446 if (!(Node.SW is null))
447 {
448 Node.SW.Parent = null;
449 this.AddNodeLocked(Node.SW);
450 Node.SW = null;
451 }
452
453 if (!(Node.SE is null))
454 {
455 Node.SE.Parent = null;
456 this.AddNodeLocked(Node.SE);
457 Node.SE = null;
458 }
459
460 return true;
461 }
462
471 public bool Moved(T Object)
472 {
473 lock (this.synchObj)
474 {
475 if (!this.RemoveLocked(Object))
476 return false;
477
478 this.AddLocked(Object);
479
480 return true;
481 }
482 }
483
489 public T[] Find(IGeoBoundingBox Box)
490 {
491 return this.Find(Box, 0, int.MaxValue, null, null);
492 }
493
500 public T[] Find(IGeoBoundingBox Box, int MaxCount)
501 {
502 return this.Find(Box, 0, MaxCount, null, null);
503 }
504
512 public T[] Find(IGeoBoundingBox Box, int Offset, int MaxCount)
513 {
514 return this.Find(Box, Offset, MaxCount, null, null);
515 }
516
524 public T[] Find(IGeoBoundingBox Box, Regex GeoIdPattern)
525 {
526 return this.Find(Box, 0, int.MaxValue, GeoIdPattern, null);
527 }
528
537 public T[] Find(IGeoBoundingBox Box, int MaxCount, Regex GeoIdPattern)
538 {
539 return this.Find(Box, 0, MaxCount, GeoIdPattern, null);
540 }
541
551 public T[] Find(IGeoBoundingBox Box, int Offset, int MaxCount, Regex GeoIdPattern)
552 {
553 return this.Find(Box, Offset, MaxCount, GeoIdPattern, null);
554 }
555
567 public T[] Find(IGeoBoundingBox Box, int Offset, int MaxCount, Regex GeoIdPattern,
568 Predicate<T> CustomFilter)
569 {
570 if (Offset < 0)
571 throw new ArgumentOutOfRangeException(nameof(Offset), "Offset must be a non-negative number.");
572
573 if (MaxCount <= 0)
574 return Array.Empty<T>();
575
576 ChunkedList<T> Result = null;
578 GeoPosition Pos;
579 GeoNode Loop;
580 T Obj;
581 int i;
582
583 lock (this.synchObj)
584 {
585 if (this.root is null)
586 return Array.Empty<T>();
587
588 Queue = new ChunkedList<GeoNode>() { this.root };
589
590 while (Queue.HasFirstItem)
591 {
592 Loop = Queue.RemoveFirst();
593 Obj = Loop.Object;
594 Pos = Obj.Location;
595
596 i = 15; // 1 = NW, 2 = NE, 4 = SW, 8 = SE
597
598 if (Pos.NorthOf(Box))
599 i &= 12;
600
601 if (Pos.SouthOf(Box))
602 i &= 3;
603
604 if (Pos.EastOf(Box))
605 i &= 5;
606
607 if (Pos.WestOf(Box))
608 i &= 10;
609
610 if (i != 0)
611 {
612 if (i == 15 &&
613 Pos.AltitudeCheck(Box) &&
614 (GeoIdPattern is null || Obj.GeoId.GeoIdPatternCheck(GeoIdPattern)) &&
615 (CustomFilter is null || CustomFilter(Obj)))
616 {
617 if (Offset >= 0)
618 {
619 if (Result is null)
620 Result = new ChunkedList<T>();
621
622 Result.Add(Obj);
623
624 if (--MaxCount <= 0)
625 return Result.ToArray();
626 }
627 else
628 Offset--;
629 }
630
631 if ((i & 1) != 0 && !(Loop.NW is null))
632 Queue.Add(Loop.NW);
633
634 if ((i & 2) != 0 && !(Loop.NE is null))
635 Queue.Add(Loop.NE);
636
637 if ((i & 4) != 0 && !(Loop.SW is null))
638 Queue.Add(Loop.SW);
639
640 if ((i & 8) != 0 && !(Loop.SE is null))
641 Queue.Add(Loop.SE);
642 }
643 }
644 }
645
646 return Result?.ToArray() ?? Array.Empty<T>();
647 }
648 }
649}
A chunked list is a linked list of chunks of objects of type T .
Definition: ChunkedList.cs:54
T RemoveFirst()
Removes the first item in the collection.
Definition: ChunkedList.cs:876
In-memory thread-safe geo-spatial collection of points (positions).
T[] Find(IGeoBoundingBox Box, int Offset, int MaxCount)
Finds items that reside inside a geo-spatial bounding box.
bool Contains(string GeoId)
Checks if the collection contains a geo-spatial object reference with a given ID.
T[] Find(IGeoBoundingBox Box, int Offset, int MaxCount, Regex GeoIdPattern)
Finds items that reside inside a geo-spatial bounding box.
T[] Find(IGeoBoundingBox Box, int MaxCount, Regex GeoIdPattern)
Finds items that reside inside a geo-spatial bounding box.
T[] Find(IGeoBoundingBox Box, Regex GeoIdPattern)
Finds items that reside inside a geo-spatial bounding box.
bool Remove(string GeoId)
Removes an object from the collection, given its ID.
T[] Find(IGeoBoundingBox Box, int Offset, int MaxCount, Regex GeoIdPattern, Predicate< T > CustomFilter)
Finds items that reside inside a geo-spatial bounding box.
IEnumerator< T > GetEnumerator()
Returns an enumerator for all the elements in the collection.
int Count
Number of items in collection
void Add(T Object)
Adds a geo-spatial object reference.
T[] Find(IGeoBoundingBox Box)
Finds items that reside inside a geo-spatial bounding box.
void CopyTo(T[] Destination, int Offset)
Copies the contents of the collection to a destination array.
bool Contains(T Object)
Checks if the collection contains a geo-spatial object reference.
GeoPositionCollection()
In-memory thread-safe geo-spatial collection of points (positions).
bool IsReadOnly
If collection is read-only.
T[] Find(IGeoBoundingBox Box, int MaxCount)
Finds items that reside inside a geo-spatial bounding box.
bool Remove(T Object)
Removes an object from the collection.
bool TryGetObject(string GeoId, out T Object)
Tries to get a geo-spatial object reference, given its ID.
bool Moved(T Object)
Each time an object has moved, the collection must be notified. This is done by calling this method....
T[] ToArray()
Returns the elements of the collection as an array.
bool Remove(string GeoId, out T Object)
Removes an object from the collection, given its ID.
Contains information about a position in a geo-spatial coordinate system.
Definition: GeoPosition.cs:17
Interface for a geo-spatial bounding box using the Mercator Projection.
Reference to an object with a geo-spatial location
Definition: ImplTypes.g.cs:58