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.text.MessageFormat;
20
21 import org.apache.commons.geometry.core.RegionLocation;
22 import org.apache.commons.geometry.core.Transform;
23 import org.apache.commons.geometry.core.partitioning.Hyperplane;
24 import org.apache.commons.geometry.core.partitioning.HyperplaneBoundedRegion;
25 import org.apache.commons.geometry.core.partitioning.HyperplaneLocation;
26 import org.apache.commons.geometry.core.partitioning.Split;
27 import org.apache.commons.numbers.core.Precision;
28
29
30
31
32
33
34
35 public final class Interval implements HyperplaneBoundedRegion<Vector1D> {
36
37 private static final Interval FULL = new Interval(null, null);
38
39
40
41
42
43 private final OrientedPoint minBoundary;
44
45
46
47
48
49 private final OrientedPoint maxBoundary;
50
51
52
53
54
55
56
57 private Interval(final OrientedPoint minBoundary, final OrientedPoint maxBoundary) {
58 this.minBoundary = minBoundary;
59 this.maxBoundary = maxBoundary;
60 }
61
62
63
64
65
66
67 public double getMin() {
68 return (minBoundary != null) ? minBoundary.getLocation() : Double.NEGATIVE_INFINITY;
69 }
70
71
72
73
74
75
76 public double getMax() {
77 return (maxBoundary != null) ? maxBoundary.getLocation() : Double.POSITIVE_INFINITY;
78 }
79
80
81
82
83
84
85
86
87 public OrientedPoint getMinBoundary() {
88 return minBoundary;
89 }
90
91
92
93
94
95
96
97
98 public OrientedPoint getMaxBoundary() {
99 return maxBoundary;
100 }
101
102
103
104
105 public boolean hasMinBoundary() {
106 return minBoundary != null;
107 }
108
109
110
111
112 public boolean hasMaxBoundary() {
113 return maxBoundary != null;
114 }
115
116
117
118
119
120 @Override
121 public boolean isInfinite() {
122 return minBoundary == null || maxBoundary == null;
123 }
124
125
126
127
128
129 @Override
130 public boolean isFinite() {
131 return !isInfinite();
132 }
133
134
135 @Override
136 public RegionLocation classify(final Vector1D pt) {
137 return classify(pt.getX());
138 }
139
140
141
142
143
144
145 public RegionLocation classify(final double location) {
146 final RegionLocation minLoc = classifyWithBoundary(location, minBoundary);
147 final RegionLocation maxLoc = classifyWithBoundary(location, maxBoundary);
148
149 if (minLoc == RegionLocation.BOUNDARY || maxLoc == RegionLocation.BOUNDARY) {
150 return RegionLocation.BOUNDARY;
151 } else if (minLoc == RegionLocation.INSIDE && maxLoc == RegionLocation.INSIDE) {
152 return RegionLocation.INSIDE;
153 }
154 return RegionLocation.OUTSIDE;
155 }
156
157
158
159
160
161
162 private RegionLocation classifyWithBoundary(final double location, final OrientedPoint boundary) {
163 if (Double.isNaN(location)) {
164 return RegionLocation.OUTSIDE;
165 } else if (boundary == null) {
166 return RegionLocation.INSIDE;
167 } else {
168 final HyperplaneLocation hyperLoc = boundary.classify(location);
169
170 if (hyperLoc == HyperplaneLocation.ON) {
171 return RegionLocation.BOUNDARY;
172 } else if (hyperLoc == HyperplaneLocation.PLUS) {
173 return RegionLocation.OUTSIDE;
174 }
175 return RegionLocation.INSIDE;
176 }
177 }
178
179
180
181
182
183
184
185 public boolean contains(final double x) {
186 return classify(x) != RegionLocation.OUTSIDE;
187 }
188
189
190
191
192
193
194
195
196
197
198
199 @Override
200 public Vector1D project(final Vector1D pt) {
201
202 OrientedPoint boundary = null;
203
204 if (minBoundary != null && maxBoundary != null) {
205
206 final double minOffset = minBoundary.offset(pt.getX());
207 final double maxOffset = maxBoundary.offset(pt.getX());
208
209 final double minDist = Math.abs(minOffset);
210 final double maxDist = Math.abs(maxOffset);
211
212
213
214 if (maxDist < minDist || maxOffset > 0) {
215 boundary = maxBoundary;
216 } else {
217 boundary = minBoundary;
218 }
219 } else if (minBoundary != null) {
220
221 boundary = minBoundary;
222 } else if (maxBoundary != null) {
223
224 boundary = maxBoundary;
225 }
226
227 return (boundary != null) ? boundary.project(pt) : null;
228 }
229
230
231
232
233
234 public Interval transform(final Transform<Vector1D> transform) {
235 final OrientedPoint transformedMin = (minBoundary != null) ?
236 minBoundary.transform(transform) :
237 null;
238 final OrientedPoint transformedMax = (maxBoundary != null) ?
239 maxBoundary.transform(transform) :
240 null;
241
242 return of(transformedMin, transformedMax);
243 }
244
245
246
247
248
249
250
251 @Override
252 public boolean isEmpty() {
253 return false;
254 }
255
256
257 @Override
258 public boolean isFull() {
259 return minBoundary == null && maxBoundary == null;
260 }
261
262
263 @Override
264 public double getSize() {
265 if (isInfinite()) {
266 return Double.POSITIVE_INFINITY;
267 }
268
269 return getMax() - getMin();
270 }
271
272
273
274
275
276
277 @Override
278 public double getBoundarySize() {
279 return 0;
280 }
281
282
283 @Override
284 public Vector1D getCentroid() {
285 if (isInfinite()) {
286 return null;
287 }
288
289 final double min = getMin();
290 final double max = getMax();
291
292 return Vector1D.of((0.5 * (max - min)) + min);
293 }
294
295
296 @Override
297 public Split<Interval> split(final Hyperplane<Vector1D> splitter) {
298 final OrientedPoint splitOrientedPoint = (OrientedPoint) splitter;
299 final Vector1D splitPoint = splitOrientedPoint.getPoint();
300
301 final HyperplaneLocation splitterMinLoc = (minBoundary != null) ? minBoundary.classify(splitPoint) : null;
302 final HyperplaneLocation splitterMaxLoc = (maxBoundary != null) ? maxBoundary.classify(splitPoint) : null;
303
304 Interval low = null;
305 Interval high = null;
306
307 if (splitterMinLoc != HyperplaneLocation.ON || splitterMaxLoc != HyperplaneLocation.ON) {
308
309 if (splitterMinLoc != null && splitterMinLoc != HyperplaneLocation.MINUS) {
310
311 high = this;
312 } else if (splitterMaxLoc != null && splitterMaxLoc != HyperplaneLocation.MINUS) {
313
314 low = this;
315 } else {
316
317 low = new Interval(minBoundary, OrientedPoints.createPositiveFacing(
318 splitPoint, splitOrientedPoint.getPrecision()));
319 high = new Interval(OrientedPoints.createNegativeFacing(
320 splitPoint, splitOrientedPoint.getPrecision()), maxBoundary);
321 }
322 }
323
324
325 final boolean lowIsMinus = splitOrientedPoint.isPositiveFacing();
326 final Interval minus = lowIsMinus ? low : high;
327 final Interval plus = lowIsMinus ? high : low;
328
329 return new Split<>(minus, plus);
330 }
331
332
333
334
335
336 public RegionBSPTree1D toTree() {
337 return RegionBSPTree1D.from(this);
338 }
339
340
341 @Override
342 public String toString() {
343 final StringBuilder sb = new StringBuilder();
344 sb.append(this.getClass().getSimpleName())
345 .append("[min= ")
346 .append(getMin())
347 .append(", max= ")
348 .append(getMax())
349 .append(']');
350
351 return sb.toString();
352 }
353
354
355
356
357
358
359
360
361
362
363 public static Interval of(final double a, final double b, final Precision.DoubleEquivalence precision) {
364 validateIntervalValues(a, b);
365
366 final double min = Math.min(a, b);
367 final double max = Math.max(a, b);
368
369 final OrientedPoint minBoundary = Double.isFinite(min) ?
370 OrientedPoints.fromLocationAndDirection(min, false, precision) :
371 null;
372
373 final OrientedPoint maxBoundary = Double.isFinite(max) ?
374 OrientedPoints.fromLocationAndDirection(max, true, precision) :
375 null;
376
377 if (minBoundary == null && maxBoundary == null) {
378 return FULL;
379 }
380
381 return new Interval(minBoundary, maxBoundary);
382 }
383
384
385
386
387
388
389
390
391
392
393 public static Interval of(final Vector1D a, final Vector1D b, final Precision.DoubleEquivalence precision) {
394 return of(a.getX(), b.getX(), precision);
395 }
396
397
398
399
400
401
402
403
404
405
406
407
408
409 public static Interval of(final OrientedPoint a, final OrientedPoint b) {
410
411 validateBoundaryRelationship(a, b);
412
413 final boolean hasA = a != null;
414 final boolean hasB = b != null;
415
416 if (!hasA && !hasB) {
417
418 return FULL;
419 }
420
421
422 final OrientedPoint minBoundary = ((hasA && !a.isPositiveFacing()) || (hasB && b.isPositiveFacing())) ? a : b;
423 final OrientedPoint maxBoundary = ((hasA && a.isPositiveFacing()) || (hasB && !b.isPositiveFacing())) ? a : b;
424
425
426 final double minLoc = (minBoundary != null) ? minBoundary.getLocation() : Double.NEGATIVE_INFINITY;
427 final double maxLoc = (maxBoundary != null) ? maxBoundary.getLocation() : Double.POSITIVE_INFINITY;
428
429 validateIntervalValues(minLoc, maxLoc);
430
431
432 return new Interval(
433 Double.isFinite(minLoc) ? minBoundary : null,
434 Double.isFinite(maxLoc) ? maxBoundary : null);
435 }
436
437
438
439
440
441
442 public static Interval min(final double min, final Precision.DoubleEquivalence precision) {
443 return of(min, Double.POSITIVE_INFINITY, precision);
444 }
445
446
447
448
449
450
451 public static Interval max(final double max, final Precision.DoubleEquivalence precision) {
452 return of(Double.NEGATIVE_INFINITY, max, precision);
453 }
454
455
456
457
458
459
460 public static Interval point(final double location, final Precision.DoubleEquivalence precision) {
461 return of(location, location, precision);
462 }
463
464
465
466
467
468
469 public static Interval full() {
470 return FULL;
471 }
472
473
474
475
476
477
478
479
480 private static void validateBoundaryRelationship(final OrientedPoint a, final OrientedPoint b) {
481 if (a != null && b != null) {
482 if (a.isPositiveFacing() == b.isPositiveFacing()) {
483 throw new IllegalArgumentException(
484 MessageFormat.format("Invalid interval: hyperplanes have same orientation: {0}, {1}", a, b));
485 }
486
487 if (a.classify(b.getPoint()) == HyperplaneLocation.PLUS ||
488 b.classify(a.getPoint()) == HyperplaneLocation.PLUS) {
489 throw new IllegalArgumentException(
490 MessageFormat.format("Invalid interval: hyperplanes do not form interval: {0}, {1}", a, b));
491 }
492 }
493 }
494
495
496
497
498
499
500
501
502 private static void validateIntervalValues(final double a, final double b) {
503 if (Double.isNaN(a) || Double.isNaN(b) ||
504 (Double.isInfinite(a) && Double.compare(a, b) == 0)) {
505
506 throw new IllegalArgumentException(
507 MessageFormat.format("Invalid interval values: [{0}, {1}]", a, b));
508 }
509 }
510 }