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