1 package org.djutils.quadtree;
2
3 import java.io.Serializable;
4 import java.util.Collection;
5 import java.util.Iterator;
6 import java.util.LinkedHashSet;
7 import java.util.Set;
8
9 import org.djutils.exceptions.Throw;
10
11
12
13
14
15
16
17
18
19
20
21 public class QuadTree<T extends Envelope> implements Collection<T>, Serializable
22 {
23
24 private static final long serialVersionUID = 20200904L;
25
26
27 private final int maximumLoad;
28
29
30 private final double minimumSize;
31
32
33 private final SubTree<T> tree;
34
35
36 private int totalSubTrees = 0;
37
38
39
40
41
42
43
44
45
46
47 public QuadTree(final int maximumLoad, final double minimumSize, final double left, final double bottom, final double right,
48 final double top)
49 {
50 Throw.when(left >= right, IllegalArgumentException.class, "left (%f) must be less than right (%f)", left, right);
51 Throw.when(bottom >= top, IllegalArgumentException.class, "bottom (%f) must be less than top (%f)", bottom, top);
52 this.maximumLoad = maximumLoad;
53 this.minimumSize = minimumSize;
54 this.tree = new SubTree<T>(this, new Rectangle(left, bottom, right, top));
55 }
56
57
58
59
60
61 public int getMaxLoad()
62 {
63 return this.maximumLoad;
64 }
65
66
67
68
69
70 public double getMinimumSize()
71 {
72 return this.minimumSize;
73 }
74
75 @Override
76 public int size()
77 {
78 return this.tree.size();
79 }
80
81 @Override
82 public boolean isEmpty()
83 {
84 return this.tree.size() == 0;
85 }
86
87 @Override
88 public boolean contains(final Object o)
89 {
90 if (!(o instanceof Envelope))
91 {
92 return false;
93 }
94 @SuppressWarnings("unchecked")
95 T t = (T) o;
96 return this.tree.recursiveContains(new RectangleAndPayload<T>(t.getBoundingRectangle(), t));
97 }
98
99 @Override
100 public Iterator<T> iterator()
101 {
102 return iterator(this.tree.getBoundingBox());
103 }
104
105
106
107
108
109
110
111 public Iterator<T> iterator(final Rectangle searchArea)
112 {
113 return collect(searchArea).iterator();
114 }
115
116 @Override
117 public Object[] toArray()
118 {
119 return collect(this.tree.getBoundingBox()).toArray();
120 }
121
122 @SuppressWarnings("hiding")
123 @Override
124 public <T> T[] toArray(final T[] a)
125 {
126 return collect(this.tree.getBoundingBox()).toArray(a);
127 }
128
129
130
131
132
133
134 private Set<T> collect(final Rectangle searchArea)
135 {
136 Iterator<RectangleAndPayload<T>> iterator = this.tree.recursiveCollect(searchArea).iterator();
137 Set<T> result = new LinkedHashSet<>();
138 while (iterator.hasNext())
139 {
140 result.add(iterator.next().getPayload());
141 }
142 return result;
143 }
144
145 @Override
146 public boolean add(final T e)
147 {
148 return this.tree.add(new RectangleAndPayload<T>(e.getBoundingRectangle(), e));
149 }
150
151 @Override
152 public boolean remove(final Object o)
153 {
154 if (!(o instanceof Envelope))
155 {
156 return false;
157 }
158 @SuppressWarnings("unchecked")
159 T t = (T) o;
160 return this.tree.remove(new RectangleAndPayload<T>(t.getBoundingRectangle(), t));
161 }
162
163 @Override
164 public boolean containsAll(final Collection<?> c)
165 {
166 return collect(this.tree.getBoundingBox()).containsAll(c);
167 }
168
169 @Override
170 public boolean addAll(final Collection<? extends T> c)
171 {
172 boolean result = false;
173 for (T t : c)
174 {
175 result |= add(t);
176 }
177 return result;
178 }
179
180 @Override
181 public boolean removeAll(final Collection<?> c)
182 {
183 boolean result = false;
184 for (Object o : c)
185 {
186 result |= remove(o);
187 }
188 return result;
189 }
190
191 @Override
192 public boolean retainAll(final Collection<?> c)
193 {
194 throw new RuntimeException("Not (yet) implemented");
195 }
196
197 @Override
198 public void clear()
199 {
200 this.tree.clear();
201 }
202
203
204
205
206 void incrementSubTreeCount()
207 {
208 this.totalSubTrees++;
209 }
210
211
212
213
214
215 public int getSubTreeCount()
216 {
217 return this.totalSubTrees;
218 }
219
220 @Override
221 public String toString()
222 {
223 return "QuadTree [maximumLoad=" + this.maximumLoad + ", minimumSize=" + this.minimumSize + ", tree=" + this.tree + "]";
224 }
225
226
227
228
229
230
231 public String toString(final int expandDepth)
232 {
233 return "QuadTree [maximumLoad=" + this.maximumLoad + ", minimumSize=" + this.minimumSize + ", tree="
234 + this.tree.toString(expandDepth) + "]";
235 }
236
237
238
239
240
241
242 public String dump(final String indent)
243 {
244 return this.tree.dump(indent);
245 }
246
247
248
249
250
251 @SuppressWarnings("hiding")
252 class SubTree<T extends Envelope> implements Serializable
253 {
254
255 private static final long serialVersionUID = 20200904L;
256
257
258 private final QuadTree<T> root;
259
260
261 private final Rectangle boundingBox;
262
263
264 private int size = 0;
265
266
267 private SubTree<T>[] children = null;
268
269
270 private Set<RectangleAndPayload<T>> elements = null;
271
272
273
274
275
276
277 SubTree(final QuadTree<T> root, final Rectangle boundingBox)
278 {
279 this.root = root;
280 this.boundingBox = boundingBox;
281 root.incrementSubTreeCount();
282 }
283
284
285
286
287
288 public final Rectangle getBoundingBox()
289 {
290 return this.boundingBox;
291 }
292
293
294
295
296
297 public int size()
298 {
299 return this.size;
300 }
301
302
303
304
305
306
307 public boolean add(final RectangleAndPayload<T> e)
308 {
309 if (contains(e))
310 {
311 return false;
312 }
313 if (this.elements == null)
314 {
315 this.elements = new LinkedHashSet<>();
316 }
317 this.elements.add(e);
318 this.size++;
319 reBalance();
320 return true;
321 }
322
323
324
325
326
327
328 public boolean remove(final RectangleAndPayload<T> o)
329 {
330 if (this.elements.remove(o))
331 {
332 this.size--;
333 return true;
334 }
335
336 boolean result = false;
337 if (this.children != null)
338 {
339 Rectangle rectangle = o.getRectangle();
340 for (SubTree<T> child : this.children)
341 {
342 if (!child.boundingBox.intersects(rectangle))
343 {
344 continue;
345 }
346 if (child.remove(o))
347 {
348 result = true;
349 }
350 }
351 }
352 if (result)
353 {
354 this.size--;
355 }
356 return result;
357 }
358
359
360
361
362 public void clear()
363 {
364 this.elements.clear();
365 this.children = null;
366 this.size = 0;
367 }
368
369
370
371
372
373
374 public boolean contains(final RectangleAndPayload<T> o)
375 {
376 if (this.elements == null)
377 {
378 return false;
379 }
380 return recursiveContains(o);
381 }
382
383
384
385
386
387
388 boolean recursiveContains(final RectangleAndPayload<T> o)
389 {
390 if ((!this.boundingBox.intersects(o.getRectangle())) || (this.elements == null))
391 {
392 return false;
393 }
394 for (RectangleAndPayload<T> element : this.elements)
395 {
396 if (element.equals(o))
397 {
398 return true;
399 }
400 }
401 if (this.children == null)
402 {
403 return false;
404 }
405 for (SubTree<T> child : this.children)
406 {
407 if (child.recursiveContains(o))
408 {
409 return true;
410 }
411 }
412 return false;
413 }
414
415
416
417
418
419
420 public Set<RectangleAndPayload<T>> recursiveCollect(final Rectangle rectangle)
421 {
422 Set<RectangleAndPayload<T>> result = new LinkedHashSet<>();
423 if (!this.boundingBox.intersects(rectangle))
424 {
425 return result;
426 }
427 if (this.elements != null)
428 {
429 for (RectangleAndPayload<T> element : this.elements)
430 {
431 if (element.getRectangle().intersects(rectangle))
432 {
433 result.add(element);
434 }
435 }
436 }
437 if (this.children != null)
438 {
439 for (SubTree<T> child : this.children)
440 {
441 result.addAll(child.recursiveCollect(rectangle));
442 }
443 }
444 return result;
445 }
446
447
448
449
450 @SuppressWarnings("unchecked")
451 private void reBalance()
452 {
453 if (this.elements.size() < this.root.getMaxLoad() || this.boundingBox.getWidth() < this.root.getMinimumSize()
454 || this.boundingBox.getHeight() < this.root.getMinimumSize())
455 {
456 return;
457 }
458
459 double cX = (this.boundingBox.getLeft() + this.boundingBox.getRight()) / 2;
460 double cY = (this.boundingBox.getBottom() + this.boundingBox.getTop()) / 2;
461 int canMove = 0;
462
463
464
465
466
467
468
469
470
471
472 canMove = this.elements.size();
473 if (canMove == 0 || canMove < this.root.getMaxLoad() && this.children == null)
474 {
475
476 return;
477 }
478
479 if (this.children == null)
480 {
481 this.children = new SubTree[] {
482 new SubTree<T>(this.root,
483 new Rectangle(this.boundingBox.getLeft(), this.boundingBox.getBottom(), cX, cY)),
484 new SubTree<T>(this.root,
485 new Rectangle(cX, this.boundingBox.getBottom(), this.boundingBox.getRight(), cY)),
486 new SubTree<T>(this.root, new Rectangle(this.boundingBox.getLeft(), cY, cX, this.boundingBox.getTop())),
487 new SubTree<T>(this.root,
488 new Rectangle(cX, cY, this.boundingBox.getRight(), this.boundingBox.getTop()))};
489 }
490 Iterator<RectangleAndPayload<T>> iterator = this.elements.iterator();
491 while (iterator.hasNext())
492 {
493 RectangleAndPayload<T> e = iterator.next();
494 if (e.getRectangle().contains(this.boundingBox))
495 {
496 continue;
497 }
498 boolean added = false;
499 for (SubTree<T> child : this.children)
500 {
501 if (e.getRectangle().intersects(child.boundingBox))
502 {
503 added |= child.add(e);
504 }
505 }
506 if (added)
507 {
508 iterator.remove();
509 }
510 else
511 {
512 System.out.println("ERROR: Could not add " + e + " to any of the children");
513 }
514 }
515
516 }
517
518 @Override
519 public String toString()
520 {
521 return "SubTree [boundingBox=" + this.boundingBox + ", size=" + this.size + ", children=" + this.children
522 + (this.elements == null ? "elements=null" : ", elements.size=" + this.elements.size()) + "]";
523 }
524
525
526
527
528
529
530 public String toString(final int expandDepth)
531 {
532 if (expandDepth > 0)
533 {
534 return "SubTree [boundingBox=" + this.boundingBox + ", size=" + this.size + ", children="
535 + (this.children != null ? "[SW:" + this.children[0].toString(expandDepth - 1) + ", SE:"
536 + this.children[1].toString(expandDepth - 1) + ", NW:"
537 + this.children[2].toString(expandDepth - 1) + ", NE:"
538 + this.children[3].toString(expandDepth - 1) + "]" : "null")
539 + ", elements.size=" + this.elements.size() + "]";
540 }
541 else
542 {
543 return toString();
544 }
545 }
546
547
548
549
550
551
552 public String dump(final String indent)
553 {
554 StringBuilder result = new StringBuilder();
555 result.append(indent);
556 result.append("SubTree [size=");
557 result.append(this.size);
558 result.append("] ");
559 result.append(this.boundingBox);
560 result.append("\n");
561 String subIndent = indent + " ";
562 Iterator<RectangleAndPayload<T>> iterator = this.elements.iterator();
563 for (int i = 0; i < this.elements.size(); i++)
564 {
565 result.append(subIndent);
566 result.append(i);
567 result.append(" ");
568 result.append(iterator.next());
569 result.append("\n");
570 }
571 if (this.children != null)
572 {
573 result.append(subIndent);
574 result.append("SW");
575 result.append("\n");
576 result.append(this.children[0].dump(subIndent));
577 result.append(subIndent);
578 result.append("SE");
579 result.append("\n");
580 result.append(this.children[1].dump(subIndent));
581 result.append(subIndent);
582 result.append("NW");
583 result.append("\n");
584 result.append(this.children[2].dump(subIndent));
585 result.append(subIndent);
586 result.append("NE");
587 result.append("\n");
588 result.append(this.children[3].dump(subIndent));
589 }
590 return result.toString();
591 }
592
593 }
594
595 }
596
597
598
599
600
601 class RectangleAndPayload<T extends Object> implements Serializable
602 {
603
604 private static final long serialVersionUID = 20200904L;
605
606
607 private final Rectangle rectangle;
608
609
610 private final T payload;
611
612
613
614
615
616
617 RectangleAndPayload(final Rectangle rectangle, final T payload)
618 {
619 this.rectangle = rectangle;
620 this.payload = payload;
621 }
622
623
624
625
626
627 public Rectangle getRectangle()
628 {
629 return this.rectangle;
630 }
631
632
633
634
635
636 public T getPayload()
637 {
638 return this.payload;
639 }
640
641 @Override
642 public String toString()
643 {
644 return "RectangleAndPayload [rectangle=" + this.rectangle + ", payload=" + this.payload + "]";
645 }
646
647 @Override
648 public int hashCode()
649 {
650 final int prime = 31;
651 int result = 1;
652 result = prime * result + ((this.payload == null) ? 0 : this.payload.hashCode());
653 result = prime * result + ((this.rectangle == null) ? 0 : this.rectangle.hashCode());
654 return result;
655 }
656
657 @Override
658 public boolean equals(final Object obj)
659 {
660 if (this == obj)
661 {
662 return true;
663 }
664 if (obj == null)
665 {
666 return false;
667 }
668 if (getClass() != obj.getClass())
669 {
670 return false;
671 }
672 @SuppressWarnings("rawtypes")
673 RectangleAndPayload other = (RectangleAndPayload) obj;
674 if (this.payload == null)
675 {
676 if (other.payload != null)
677 {
678 return false;
679 }
680 }
681 else if (!this.payload.equals(other.payload))
682 {
683 return false;
684 }
685 if (this.rectangle == null)
686 {
687 if (other.rectangle != null)
688 {
689 return false;
690 }
691 }
692 else if (!this.rectangle.equals(other.rectangle))
693 {
694 return false;
695 }
696 return true;
697 }
698
699 }