1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.geometry.euclidean.oned;
18
19 import java.util.ArrayList;
20 import java.util.Comparator;
21 import java.util.List;
22 import java.util.Objects;
23 import java.util.function.BiConsumer;
24
25 import org.apache.commons.geometry.core.RegionLocation;
26 import org.apache.commons.geometry.core.Transform;
27 import org.apache.commons.geometry.core.partitioning.Hyperplane;
28 import org.apache.commons.geometry.core.partitioning.Split;
29 import org.apache.commons.geometry.core.partitioning.bsp.AbstractBSPTree;
30 import org.apache.commons.geometry.core.partitioning.bsp.AbstractRegionBSPTree;
31 import org.apache.commons.geometry.core.partitioning.bsp.RegionCutRule;
32
33
34
35
36 public final class RegionBSPTree1D extends AbstractRegionBSPTree<Vector1D, RegionBSPTree1D.RegionNode1D> {
37
38 private static final Comparator<BoundaryPair> BOUNDARY_PAIR_COMPARATOR =
39 Comparator.comparingDouble(BoundaryPair::getMinValue);
40
41
42
43 public RegionBSPTree1D() {
44 this(false);
45 }
46
47
48
49
50
51
52 public RegionBSPTree1D(final boolean full) {
53 super(full);
54 }
55
56
57
58
59
60 public RegionBSPTree1D copy() {
61 final RegionBSPTree1D result = RegionBSPTree1D.empty();
62 result.copy(this);
63
64 return result;
65 }
66
67
68
69
70
71 public void add(final Interval interval) {
72 union(intervalToTree(interval));
73 }
74
75
76
77
78
79
80 public RegionLocation classify(final double x) {
81 return classify(Vector1D.of(x));
82 }
83
84
85
86
87
88
89
90 public boolean contains(final double x) {
91 return contains(Vector1D.of(x));
92 }
93
94
95
96
97
98
99 @Override
100 public double getBoundarySize() {
101 return 0;
102 }
103
104
105 @Override
106 public Vector1D project(final Vector1D pt) {
107
108
109 final BoundaryProjector1D projector = new BoundaryProjector1D(pt);
110 accept(projector);
111
112 return projector.getProjected();
113 }
114
115
116
117
118
119
120
121
122
123 @Override
124 public Split<RegionBSPTree1D> split(final Hyperplane<Vector1D> splitter) {
125 return split(splitter, RegionBSPTree1D.empty(), RegionBSPTree1D.empty());
126 }
127
128
129
130
131
132
133 public double getMin() {
134 double min = Double.POSITIVE_INFINITY;
135
136 RegionNode1D node = getRoot();
137 OrientedPoint pt;
138
139 while (!node.isLeaf()) {
140 pt = (OrientedPoint) node.getCutHyperplane();
141
142 min = pt.getLocation();
143 node = pt.isPositiveFacing() ? node.getMinus() : node.getPlus();
144 }
145
146 return node.isInside() ? Double.NEGATIVE_INFINITY : min;
147 }
148
149
150
151
152
153
154 public double getMax() {
155 double max = Double.NEGATIVE_INFINITY;
156
157 RegionNode1D node = getRoot();
158 OrientedPoint pt;
159
160 while (!node.isLeaf()) {
161 pt = (OrientedPoint) node.getCutHyperplane();
162
163 max = pt.getLocation();
164 node = pt.isPositiveFacing() ? node.getPlus() : node.getMinus();
165 }
166
167 return node.isInside() ? Double.POSITIVE_INFINITY : max;
168 }
169
170
171
172
173
174
175 public List<Interval> toIntervals() {
176
177 final List<BoundaryPair> boundaryPairs = new ArrayList<>();
178
179 visitInsideIntervals((min, max) -> boundaryPairs.add(new BoundaryPair(min, max)));
180 boundaryPairs.sort(BOUNDARY_PAIR_COMPARATOR);
181
182 final List<Interval> intervals = new ArrayList<>();
183
184 BoundaryPair start = null;
185 BoundaryPair end = null;
186
187 for (final BoundaryPair current : boundaryPairs) {
188 if (start == null) {
189 start = current;
190 end = current;
191 } else if (Objects.equals(end.getMax(), current.getMin())) {
192
193 end = current;
194 } else {
195
196 intervals.add(createInterval(start, end));
197
198
199 start = current;
200 end = current;
201 }
202 }
203
204 if (start != null) {
205 intervals.add(createInterval(start, end));
206 }
207
208 return intervals;
209 }
210
211
212
213
214
215
216
217
218
219 private Interval createInterval(final BoundaryPair start, final BoundaryPair end) {
220 OrientedPoint min = start.getMin();
221 OrientedPoint max = end.getMax();
222
223
224
225
226
227 if (min != null && min.isPositiveFacing()) {
228 min = min.reverse();
229 }
230 if (max != null && !max.isPositiveFacing()) {
231 max = max.reverse();
232 }
233
234 return Interval.of(min, max);
235 }
236
237
238
239
240
241
242 private void visitInsideIntervals(final BiConsumer<OrientedPoint, OrientedPoint> visitor) {
243 for (final RegionNode1D node : nodes()) {
244 if (node.isInside()) {
245 node.visitNodeInterval(visitor);
246 }
247 }
248 }
249
250
251 @Override
252 protected RegionNode1D createNode() {
253 return new RegionNode1D(this);
254 }
255
256
257 @Override
258 protected RegionSizeProperties<Vector1D> computeRegionSizeProperties() {
259 final RegionSizePropertiesVisitor visitor = new RegionSizePropertiesVisitor();
260
261 visitInsideIntervals(visitor);
262
263 return visitor.getRegionSizeProperties();
264 }
265
266
267
268
269
270
271
272 @Override
273 protected boolean swapsInsideOutside(final Transform<Vector1D> transform) {
274 return false;
275 }
276
277
278
279
280 public static RegionBSPTree1D full() {
281 return new RegionBSPTree1D(true);
282 }
283
284
285
286
287 public static RegionBSPTree1D empty() {
288 return new RegionBSPTree1D(false);
289 }
290
291
292
293
294
295
296
297
298 public static RegionBSPTree1D from(final Interval interval, final Interval... more) {
299 final RegionBSPTree1D tree = intervalToTree(interval);
300
301 for (final Interval additional : more) {
302 tree.add(additional);
303 }
304
305 return tree;
306 }
307
308
309
310
311
312 public static RegionBSPTree1D from(final Iterable<Interval> intervals) {
313 final RegionBSPTree1D tree = new RegionBSPTree1D(false);
314
315 for (final Interval interval : intervals) {
316 tree.add(interval);
317 }
318
319 return tree;
320 }
321
322
323
324
325
326 private static RegionBSPTree1D intervalToTree(final Interval interval) {
327 final OrientedPoint minBoundary = interval.getMinBoundary();
328 final OrientedPoint maxBoundary = interval.getMaxBoundary();
329
330 final RegionBSPTree1D tree = full();
331
332 RegionNode1D node = tree.getRoot();
333
334 if (minBoundary != null) {
335 tree.setNodeCut(node, minBoundary.span(), tree.getSubtreeInitializer(RegionCutRule.MINUS_INSIDE));
336
337 node = node.getMinus();
338 }
339
340 if (maxBoundary != null) {
341 tree.setNodeCut(node, maxBoundary.span(), tree.getSubtreeInitializer(RegionCutRule.MINUS_INSIDE));
342 }
343
344 return tree;
345 }
346
347
348
349 public static final class RegionNode1D extends AbstractRegionBSPTree.AbstractRegionNode<Vector1D, RegionNode1D> {
350
351
352
353 private RegionNode1D(final AbstractBSPTree<Vector1D, RegionNode1D> tree) {
354 super(tree);
355 }
356
357
358
359
360
361
362 public Interval getNodeRegion() {
363 final NodeRegionVisitor visitor = new NodeRegionVisitor();
364 visitNodeInterval(visitor);
365
366 return visitor.getInterval();
367 }
368
369
370
371
372
373
374 private void visitNodeInterval(final BiConsumer<? super OrientedPoint, ? super OrientedPoint> visitor) {
375 OrientedPoint min = null;
376 OrientedPoint max = null;
377
378 OrientedPoint pt;
379 RegionNode1D child = this;
380 RegionNode1D parent;
381
382 while ((min == null || max == null) && (parent = child.getParent()) != null) {
383 pt = (OrientedPoint) parent.getCutHyperplane();
384
385 if ((pt.isPositiveFacing() && child.isMinus()) ||
386 (!pt.isPositiveFacing() && child.isPlus())) {
387
388 if (max == null) {
389 max = pt;
390 }
391 } else if (min == null) {
392 min = pt;
393 }
394
395 child = parent;
396 }
397
398 visitor.accept(min, max);
399 }
400
401
402 @Override
403 protected RegionNode1D getSelf() {
404 return this;
405 }
406 }
407
408
409
410 private static final class BoundaryPair {
411
412
413 private final OrientedPoint min;
414
415
416 private final OrientedPoint max;
417
418
419
420
421
422 BoundaryPair(final OrientedPoint min, final OrientedPoint max) {
423 this.min = min;
424 this.max = max;
425 }
426
427
428
429
430 public OrientedPoint getMin() {
431 return min;
432 }
433
434
435
436
437 public OrientedPoint getMax() {
438 return max;
439 }
440
441
442
443
444
445
446 public double getMinValue() {
447 return (min != null) ? min.getLocation() : Double.NEGATIVE_INFINITY;
448 }
449 }
450
451
452
453 private static final class BoundaryProjector1D extends BoundaryProjector<Vector1D, RegionNode1D> {
454
455
456
457 BoundaryProjector1D(final Vector1D point) {
458 super(point);
459 }
460
461
462 @Override
463 protected Vector1D disambiguateClosestPoint(final Vector1D target, final Vector1D a, final Vector1D b) {
464 final int cmp = Vector1D.COORDINATE_ASCENDING_ORDER.compare(a, b);
465
466 if (target.isInfinite() && target.getX() > 0) {
467
468 return cmp < 0 ? b : a;
469 }
470
471
472 return cmp < 0 ? a : b;
473 }
474 }
475
476
477
478 private static final class NodeRegionVisitor implements BiConsumer<OrientedPoint, OrientedPoint> {
479
480
481 private OrientedPoint min;
482
483
484 private OrientedPoint max;
485
486
487 @Override
488 public void accept(final OrientedPoint minBoundary, final OrientedPoint maxBoundary) {
489
490 this.min = (minBoundary != null && minBoundary.isPositiveFacing()) ? minBoundary.reverse() : minBoundary;
491 this.max = (maxBoundary != null && !maxBoundary.isPositiveFacing()) ? maxBoundary.reverse() : maxBoundary;
492 }
493
494
495
496
497 public Interval getInterval() {
498 return Interval.of(min, max);
499 }
500 }
501
502
503
504 private static final class RegionSizePropertiesVisitor implements BiConsumer<OrientedPoint, OrientedPoint> {
505
506 private int count;
507
508
509 private double size;
510
511
512 private double rawCentroidSum;
513
514
515 private double scaledCentroidSum;
516
517
518 @Override
519 public void accept(final OrientedPoint min, final OrientedPoint max) {
520 ++count;
521
522 final double minLoc = (min != null) ? min.getLocation() : Double.NEGATIVE_INFINITY;
523 final double maxLoc = (max != null) ? max.getLocation() : Double.POSITIVE_INFINITY;
524
525 final double intervalSize = maxLoc - minLoc;
526 final double intervalCentroid = 0.5 * (maxLoc + minLoc);
527
528 size += intervalSize;
529 rawCentroidSum += intervalCentroid;
530 scaledCentroidSum += intervalSize * intervalCentroid;
531 }
532
533
534
535
536
537 public RegionSizeProperties<Vector1D> getRegionSizeProperties() {
538 Vector1D centroid = null;
539
540 if (count > 0 && Double.isFinite(size)) {
541 if (size > 0) {
542
543 centroid = Vector1D.of(scaledCentroidSum / size);
544 } else {
545
546
547 centroid = Vector1D.of(rawCentroidSum / count);
548 }
549 }
550
551 return new RegionSizeProperties<>(size, centroid);
552 }
553 }
554 }