1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.hipparchus.geometry.spherical.oned;
23
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.NoSuchElementException;
29
30 import org.hipparchus.exception.LocalizedCoreFormats;
31 import org.hipparchus.exception.MathIllegalArgumentException;
32 import org.hipparchus.exception.MathIllegalStateException;
33 import org.hipparchus.exception.MathRuntimeException;
34 import org.hipparchus.geometry.LocalizedGeometryFormats;
35 import org.hipparchus.geometry.partitioning.AbstractRegion;
36 import org.hipparchus.geometry.partitioning.BSPTree;
37 import org.hipparchus.geometry.partitioning.BoundaryProjection;
38 import org.hipparchus.geometry.partitioning.Side;
39 import org.hipparchus.util.FastMath;
40 import org.hipparchus.util.MathUtils;
41 import org.hipparchus.util.Precision;
42
43
44
45
46
47
48
49
50
51
52 public class ArcsSet extends AbstractRegion<Sphere1D, S1Point, LimitAngle, SubLimitAngle, Sphere1D, S1Point, LimitAngle, SubLimitAngle>
53 implements Iterable<double[]> {
54
55
56
57
58
59 public ArcsSet(final double tolerance)
60 throws MathIllegalArgumentException {
61 super(tolerance);
62 Sphere1D.checkTolerance(tolerance);
63 }
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79 public ArcsSet(final double lower, final double upper, final double tolerance)
80 throws MathIllegalArgumentException {
81 super(buildTree(lower, upper, tolerance), tolerance);
82 Sphere1D.checkTolerance(tolerance);
83 }
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98 public ArcsSet(final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> tree, final double tolerance)
99 throws InconsistentStateAt2PiWrapping, MathIllegalArgumentException {
100 super(tree, tolerance);
101 Sphere1D.checkTolerance(tolerance);
102 check2PiConsistency();
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 public ArcsSet(final Collection<SubLimitAngle> boundary, final double tolerance)
129 throws InconsistentStateAt2PiWrapping, MathIllegalArgumentException {
130 super(boundary, tolerance);
131 Sphere1D.checkTolerance(tolerance);
132 check2PiConsistency();
133 }
134
135
136
137
138
139
140
141
142
143 private static BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle>
144 buildTree(final double lower, final double upper, final double tolerance)
145 throws MathIllegalArgumentException {
146
147 Sphere1D.checkTolerance(tolerance);
148 if (Precision.equals(lower, upper, 0) || (upper - lower) >= MathUtils.TWO_PI) {
149
150 return new BSPTree<>(Boolean.TRUE);
151 } else if (lower > upper) {
152 throw new MathIllegalArgumentException(LocalizedCoreFormats.ENDPOINTS_NOT_AN_INTERVAL,
153 lower, upper, true);
154 }
155
156
157 final double normalizedLower = MathUtils.normalizeAngle(lower, FastMath.PI);
158 final double normalizedUpper = normalizedLower + (upper - lower);
159 final SubLimitAngle lowerCut = new LimitAngle(new S1Point(normalizedLower), false, tolerance).wholeHyperplane();
160
161 if (normalizedUpper <= MathUtils.TWO_PI) {
162
163 final SubLimitAngle upperCut = new LimitAngle(new S1Point(normalizedUpper), true, tolerance).wholeHyperplane();
164 return new BSPTree<>(lowerCut,
165 new BSPTree<>(Boolean.FALSE),
166 new BSPTree<>(upperCut,
167 new BSPTree<>(Boolean.FALSE),
168 new BSPTree<>(Boolean.TRUE),
169 null),
170 null);
171 } else {
172
173 final SubLimitAngle upperCut = new LimitAngle(new S1Point(normalizedUpper - MathUtils.TWO_PI), true, tolerance).wholeHyperplane();
174 return new BSPTree<>(lowerCut,
175 new BSPTree<>(upperCut,
176 new BSPTree<>(Boolean.FALSE),
177 new BSPTree<>(Boolean.TRUE),
178 null),
179 new BSPTree<>(Boolean.TRUE),
180 null);
181 }
182
183 }
184
185
186
187
188
189 private void check2PiConsistency() throws InconsistentStateAt2PiWrapping {
190
191
192 BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> root = getTree(false);
193 if (root.getCut() == null) {
194 return;
195 }
196
197
198 final Boolean stateBefore = (Boolean) getFirstLeaf(root).getAttribute();
199
200
201 final Boolean stateAfter = (Boolean) getLastLeaf(root).getAttribute();
202
203 if (stateBefore ^ stateAfter) {
204 throw new InconsistentStateAt2PiWrapping();
205 }
206
207 }
208
209
210
211
212
213 private BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle>
214 getFirstLeaf(final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> root) {
215
216 if (root.getCut() == null) {
217 return root;
218 }
219
220
221 BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> smallest = null;
222 for (BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> n = root; n != null; n = previousInternalNode(n)) {
223 smallest = n;
224 }
225
226 return leafBefore(smallest);
227
228 }
229
230
231
232
233
234 private BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle>
235 getLastLeaf(final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> root) {
236
237 if (root.getCut() == null) {
238 return root;
239 }
240
241
242 BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> largest = null;
243 for (BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> n = root; n != null; n = nextInternalNode(n)) {
244 largest = n;
245 }
246
247 return leafAfter(largest);
248
249 }
250
251
252
253
254
255 private BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> getFirstArcStart() {
256
257
258 BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> node = getTree(false);
259 if (node.getCut() == null) {
260 return null;
261 }
262
263
264 node = getFirstLeaf(node).getParent();
265
266
267 while (node != null && !isArcStart(node)) {
268 node = nextInternalNode(node);
269 }
270
271 return node;
272
273 }
274
275
276
277
278
279 private boolean isArcStart(final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> node) {
280
281 if ((Boolean) leafBefore(node).getAttribute()) {
282
283 return false;
284 }
285
286 if (!(Boolean) leafAfter(node).getAttribute()) {
287
288 return false;
289 }
290
291
292
293 return true;
294
295 }
296
297
298
299
300
301 private boolean isArcEnd(final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> node) {
302
303 if (!(Boolean) leafBefore(node).getAttribute()) {
304
305 return false;
306 }
307
308 if ((Boolean) leafAfter(node).getAttribute()) {
309
310 return false;
311 }
312
313
314
315 return true;
316
317 }
318
319
320
321
322
323
324 private BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle>
325 nextInternalNode(BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> node) {
326
327 if (childAfter(node).getCut() != null) {
328
329 return leafAfter(node).getParent();
330 }
331
332
333 while (isAfterParent(node)) {
334 node = node.getParent();
335 }
336 return node.getParent();
337
338 }
339
340
341
342
343
344
345 private BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle>
346 previousInternalNode(BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> node) {
347
348 if (childBefore(node).getCut() != null) {
349
350 return leafBefore(node).getParent();
351 }
352
353
354 while (isBeforeParent(node)) {
355 node = node.getParent();
356 }
357 return node.getParent();
358
359 }
360
361
362
363
364
365 private BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle>
366 leafBefore(BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> node) {
367
368 node = childBefore(node);
369 while (node.getCut() != null) {
370 node = childAfter(node);
371 }
372
373 return node;
374
375 }
376
377
378
379
380
381 private BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle>
382 leafAfter(BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> node) {
383
384 node = childAfter(node);
385 while (node.getCut() != null) {
386 node = childBefore(node);
387 }
388
389 return node;
390
391 }
392
393
394
395
396
397 private boolean isBeforeParent(final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> node) {
398 final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> parent = node.getParent();
399 if (parent == null) {
400 return false;
401 } else {
402 return node == childBefore(parent);
403 }
404 }
405
406
407
408
409
410 private boolean isAfterParent(final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> node) {
411 final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> parent = node.getParent();
412 if (parent == null) {
413 return false;
414 } else {
415 return node == childAfter(parent);
416 }
417 }
418
419
420
421
422
423 private BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle>
424 childBefore(BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> node) {
425 if (isDirect(node)) {
426
427 return node.getMinus();
428 } else {
429
430 return node.getPlus();
431 }
432 }
433
434
435
436
437
438 private BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle>
439 childAfter(BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> node) {
440 if (isDirect(node)) {
441
442 return node.getPlus();
443 } else {
444
445 return node.getMinus();
446 }
447 }
448
449
450
451
452
453 private boolean isDirect(final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> node) {
454 return node.getCut().getHyperplane().isDirect();
455 }
456
457
458
459
460
461 private double getAngle(final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> node) {
462 return node.getCut().getHyperplane().getLocation().getAlpha();
463 }
464
465
466 @Override
467 public ArcsSet buildNew(final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> tree) {
468 return new ArcsSet(tree, getTolerance());
469 }
470
471
472 @Override
473 public S1Point getInteriorPoint() {
474
475
476 double selectedPoint = Double.NaN;
477 double selectedLength = 0;
478 for (final double[] a : this) {
479 final double length = a[1] - a[0];
480 if (length > selectedLength) {
481
482 selectedPoint = 0.5 * (a[0] + a[1]);
483 selectedLength = length;
484 }
485 }
486
487 return Double.isNaN(selectedPoint) ? null : new S1Point(selectedPoint);
488
489 }
490
491
492 @Override
493 protected void computeGeometricalProperties() {
494 if (getTree(false).getCut() == null) {
495 setBarycenter(S1Point.NaN);
496 setSize(((Boolean) getTree(false).getAttribute()) ? MathUtils.TWO_PI : 0);
497 } else {
498 double size = 0.0;
499 double sum = 0.0;
500 for (final double[] a : this) {
501 final double length = a[1] - a[0];
502 size += length;
503 sum += length * (a[0] + a[1]);
504 }
505 setSize(size);
506 if (Precision.equals(size, MathUtils.TWO_PI, 0)) {
507 setBarycenter(S1Point.NaN);
508 } else if (size >= Precision.SAFE_MIN) {
509 setBarycenter(new S1Point(sum / (2 * size)));
510 } else {
511 final LimitAngle limit = getTree(false).getCut().getHyperplane();
512 setBarycenter(limit.getLocation());
513 }
514 }
515 }
516
517
518
519 @Override
520 public BoundaryProjection<Sphere1D, S1Point> projectToBoundary(final S1Point point) {
521
522
523 final double alpha = point.getAlpha();
524
525 boolean wrapFirst = false;
526 double first = Double.NaN;
527 double previous = Double.NaN;
528 for (final double[] a : this) {
529
530 if (Double.isNaN(first)) {
531
532 first = a[0];
533 }
534
535 if (!wrapFirst) {
536 if (alpha < a[0]) {
537
538
539 if (Double.isNaN(previous)) {
540
541 wrapFirst = true;
542 } else {
543 final double previousOffset = alpha - previous;
544 final double currentOffset = a[0] - alpha;
545 if (previousOffset < currentOffset) {
546 return new BoundaryProjection<>(point, new S1Point(previous), previousOffset);
547 } else {
548 return new BoundaryProjection<>(point, new S1Point(a[0]), currentOffset);
549 }
550 }
551 } else if (alpha <= a[1]) {
552
553
554 final double offset0 = a[0] - alpha;
555 final double offset1 = alpha - a[1];
556 if (offset0 < offset1) {
557 return new BoundaryProjection<>(point, new S1Point(a[1]), offset1);
558 } else {
559 return new BoundaryProjection<>(point, new S1Point(a[0]), offset0);
560 }
561 }
562 }
563 previous = a[1];
564 }
565
566 if (Double.isNaN(previous)) {
567
568
569 return new BoundaryProjection<>(point, null, MathUtils.TWO_PI);
570
571 } else {
572
573
574
575 if (wrapFirst) {
576
577 final double previousOffset = alpha - (previous - MathUtils.TWO_PI);
578 final double currentOffset = first - alpha;
579 if (previousOffset < currentOffset) {
580 return new BoundaryProjection<>(point, new S1Point(previous), previousOffset);
581 } else {
582 return new BoundaryProjection<>(point, new S1Point(first), currentOffset);
583 }
584 } else {
585
586 final double previousOffset = alpha - previous;
587 final double currentOffset = first + MathUtils.TWO_PI - alpha;
588 if (previousOffset < currentOffset) {
589 return new BoundaryProjection<>(point, new S1Point(previous), previousOffset);
590 } else {
591 return new BoundaryProjection<>(point, new S1Point(first), currentOffset);
592 }
593 }
594
595 }
596
597 }
598
599
600
601
602
603
604
605
606 public List<Arc> asList() {
607 final List<Arc> list = new ArrayList<>();
608 for (final double[] a : this) {
609 list.add(new Arc(a[0], a[1], getTolerance()));
610 }
611 return list;
612 }
613
614
615
616
617
618
619
620
621
622 @Override
623 public Iterator<double[]> iterator() {
624 return new SubArcsIterator();
625 }
626
627
628 private class SubArcsIterator implements Iterator<double[]> {
629
630
631 private final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> firstStart;
632
633
634 private BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> current;
635
636
637 private double[] pending;
638
639
640
641 SubArcsIterator() {
642
643 firstStart = getFirstArcStart();
644 current = firstStart;
645
646 if (firstStart == null) {
647
648 if ((Boolean) getFirstLeaf(getTree(false)).getAttribute()) {
649
650 pending = new double[] {
651 0, MathUtils.TWO_PI
652 };
653 } else {
654 pending = null;
655 }
656 } else {
657 selectPending();
658 }
659 }
660
661
662
663 private void selectPending() {
664
665
666 BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> start = current;
667 while (start != null && !isArcStart(start)) {
668 start = nextInternalNode(start);
669 }
670
671 if (start == null) {
672
673 current = null;
674 pending = null;
675 return;
676 }
677
678
679 BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> end = start;
680 while (end != null && !isArcEnd(end)) {
681 end = nextInternalNode(end);
682 }
683
684 if (end != null) {
685
686
687 pending = new double[] {
688 getAngle(start), getAngle(end)
689 };
690
691
692 current = end;
693
694 } else {
695
696
697 end = firstStart;
698 while (end != null && !isArcEnd(end)) {
699 end = previousInternalNode(end);
700 }
701 if (end == null) {
702
703 throw MathRuntimeException.createInternalError();
704 }
705
706
707 pending = new double[] {
708 getAngle(start), getAngle(end) + MathUtils.TWO_PI
709 };
710
711
712 current = null;
713
714 }
715
716 }
717
718
719 @Override
720 public boolean hasNext() {
721 return pending != null;
722 }
723
724
725 @Override
726 public double[] next() {
727 if (pending == null) {
728 throw new NoSuchElementException();
729 }
730 final double[] next = pending;
731 selectPending();
732 return next;
733 }
734
735
736 @Override
737 public void remove() {
738 throw new UnsupportedOperationException();
739 }
740
741 }
742
743
744
745
746
747
748
749 public Split split(final Arc arc) {
750
751 final List<Double> minus = new ArrayList<>();
752 final List<Double> plus = new ArrayList<>();
753
754 final double reference = FastMath.PI + arc.getInf();
755 final double arcLength = arc.getSup() - arc.getInf();
756
757 for (final double[] a : this) {
758 final double syncedStart = MathUtils.normalizeAngle(a[0], reference) - arc.getInf();
759 final double arcOffset = a[0] - syncedStart;
760 final double syncedEnd = a[1] - arcOffset;
761 if (syncedStart < arcLength) {
762
763 minus.add(a[0]);
764 if (syncedEnd > arcLength) {
765
766
767 final double minusToPlus = arcLength + arcOffset;
768 minus.add(minusToPlus);
769 plus.add(minusToPlus);
770 if (syncedEnd > MathUtils.TWO_PI) {
771
772
773 final double plusToMinus = MathUtils.TWO_PI + arcOffset;
774 plus.add(plusToMinus);
775 minus.add(plusToMinus);
776 minus.add(a[1]);
777 } else {
778
779 plus.add(a[1]);
780 }
781 } else {
782
783 minus.add(a[1]);
784 }
785 } else {
786
787 plus.add(a[0]);
788 if (syncedEnd > MathUtils.TWO_PI) {
789
790
791 final double plusToMinus = MathUtils.TWO_PI + arcOffset;
792 plus.add(plusToMinus);
793 minus.add(plusToMinus);
794 if (syncedEnd > MathUtils.TWO_PI + arcLength) {
795
796
797 final double minusToPlus = MathUtils.TWO_PI + arcLength + arcOffset;
798 minus.add(minusToPlus);
799 plus.add(minusToPlus);
800 plus.add(a[1]);
801 } else {
802
803 minus.add(a[1]);
804 }
805 } else {
806
807 plus.add(a[1]);
808 }
809 }
810 }
811
812 return new Split(createSplitPart(plus), createSplitPart(minus));
813
814 }
815
816
817
818
819
820
821 private void addArcLimit(final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> tree,
822 final double alpha, final boolean isStart) {
823
824 final LimitAngle limit = new LimitAngle(new S1Point(alpha), !isStart, getTolerance());
825 final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> node = tree.getCell(limit.getLocation(), getTolerance());
826 if (node.getCut() != null) {
827
828 throw MathRuntimeException.createInternalError();
829 }
830
831 node.insertCut(limit);
832 node.setAttribute(null);
833 node.getPlus().setAttribute(Boolean.FALSE);
834 node.getMinus().setAttribute(Boolean.TRUE);
835
836 }
837
838
839
840
841
842
843
844
845
846
847 private ArcsSet createSplitPart(final List<Double> limits) {
848 if (limits.isEmpty()) {
849 return null;
850 } else {
851
852
853 for (int i = 0; i < limits.size(); ++i) {
854 final int j = (i + 1) % limits.size();
855 final double lA = limits.get(i);
856 final double lB = MathUtils.normalizeAngle(limits.get(j), lA);
857 if (FastMath.abs(lB - lA) <= getTolerance()) {
858
859 if (j > 0) {
860
861 limits.remove(j);
862 limits.remove(i);
863 i = i - 1;
864 } else {
865
866
867 final double lEnd = limits.remove(limits.size() - 1);
868 final double lStart = limits.remove(0);
869 if (limits.isEmpty()) {
870
871 if (lEnd - lStart > FastMath.PI) {
872
873 return new ArcsSet(new BSPTree<>(Boolean.TRUE), getTolerance());
874 } else {
875
876 return null;
877 }
878 } else {
879
880
881
882 limits.add(limits.remove(0) + MathUtils.TWO_PI);
883 }
884 }
885 }
886 }
887
888
889 BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> tree = new BSPTree<>(Boolean.FALSE);
890 for (int i = 0; i < limits.size() - 1; i += 2) {
891 addArcLimit(tree, limits.get(i), true);
892 addArcLimit(tree, limits.get(i + 1), false);
893 }
894
895 if (tree.getCut() == null) {
896
897 return null;
898 }
899
900 return new ArcsSet(tree, getTolerance());
901
902 }
903 }
904
905
906
907 public static class Split {
908
909
910 private final ArcsSet plus;
911
912
913 private final ArcsSet minus;
914
915
916
917
918
919
920
921 private Split(final ArcsSet plus, final ArcsSet minus) {
922 this.plus = plus;
923 this.minus = minus;
924 }
925
926
927
928
929 public ArcsSet getPlus() {
930 return plus;
931 }
932
933
934
935
936 public ArcsSet getMinus() {
937 return minus;
938 }
939
940
941
942
943
944
945
946
947 public Side getSide() {
948 if (plus != null) {
949 if (minus != null) {
950 return Side.BOTH;
951 } else {
952 return Side.PLUS;
953 }
954 } else if (minus != null) {
955 return Side.MINUS;
956 } else {
957 return Side.HYPER;
958 }
959 }
960
961 }
962
963
964
965
966
967
968
969
970 public static class InconsistentStateAt2PiWrapping extends MathIllegalStateException
971 {
972
973
974 private static final long serialVersionUID = 20140107L;
975
976
977
978 public InconsistentStateAt2PiWrapping() {
979 super(LocalizedGeometryFormats.INCONSISTENT_STATE_AT_2_PI_WRAPPING);
980 }
981
982 }
983
984 }