1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.orekit;
18
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.Collection;
22
23 import org.hamcrest.Description;
24 import org.hamcrest.Matcher;
25 import org.hamcrest.SelfDescribing;
26 import org.hamcrest.TypeSafeDiagnosingMatcher;
27 import org.hamcrest.TypeSafeMatcher;
28 import org.hipparchus.geometry.euclidean.threed.Rotation;
29 import org.hipparchus.geometry.euclidean.threed.Vector3D;
30 import org.hipparchus.util.FastMath;
31 import org.hipparchus.util.Precision;
32 import org.orekit.attitudes.Attitude;
33 import org.orekit.bodies.GeodeticPoint;
34 import org.orekit.time.AbsoluteDate;
35 import org.orekit.utils.Constants;
36 import org.orekit.utils.PVCoordinates;
37
38 import static org.hamcrest.CoreMatchers.is;
39
40
41
42
43
44
45 public class OrekitMatchers {
46
47
48
49
50
51
52
53
54
55 public static Matcher<GeodeticPoint> geodeticPoint(
56 final Matcher<Double> lat,
57 final Matcher<Double> lon,
58 final Matcher<Double> alt) {
59 return new TypeSafeDiagnosingMatcher<GeodeticPoint>() {
60 @Override
61 public void describeTo(Description description) {
62 description.appendList("GeodeticPoint[", ", ", "]",
63 Arrays.<SelfDescribing>asList(lat, lon, alt));
64 }
65
66 @Override
67 protected boolean matchesSafely(GeodeticPoint item,
68 Description mismatchDescription) {
69 if (!lat.matches(item.getLatitude())) {
70 mismatchDescription.appendText("the latitude ");
71 lat.describeMismatch(item.getLatitude(),
72 mismatchDescription);
73 return false;
74 }
75 if (!lon.matches(item.getLongitude())) {
76 mismatchDescription.appendText("the longitude ");
77 lon.describeMismatch(item.getLongitude(),
78 mismatchDescription);
79 return false;
80 }
81 if (!alt.matches(item.getAltitude())) {
82 mismatchDescription.appendText("the altitude ");
83 alt.describeMismatch(item.getAltitude(),
84 mismatchDescription);
85 return false;
86 }
87 return true;
88 }
89 };
90 }
91
92
93
94
95
96
97
98
99
100 public static Matcher<GeodeticPoint> geodeticPoint(double lat,
101 double lon,
102 double alt) {
103 return geodeticPoint(is(lat), is(lon), is(alt));
104 }
105
106
107
108
109
110
111
112
113
114 public static Matcher<GeodeticPoint> geodeticPointCloseTo(
115 GeodeticPoint expected, double absTol) {
116 double angularAbsTol = absTol / Constants.WGS84_EARTH_EQUATORIAL_RADIUS;
117 return geodeticPoint(closeTo(expected.getLatitude(), angularAbsTol),
118 closeTo(expected.getLongitude(), angularAbsTol),
119 closeTo(expected.getAltitude(), absTol));
120 }
121
122
123
124
125
126
127
128
129
130 public static Matcher<GeodeticPoint> geodeticPointCloseTo(
131 GeodeticPoint expected, int ulps) {
132 return geodeticPoint(relativelyCloseTo(expected.getLatitude(), ulps),
133 relativelyCloseTo(expected.getLongitude(), ulps),
134 relativelyCloseTo(expected.getAltitude(), ulps));
135 }
136
137
138
139
140
141
142
143
144
145 public static Matcher<Vector3D> vector(final Matcher<Double> x,
146 final Matcher<Double> y,
147 final Matcher<Double> z) {
148 return new TypeSafeDiagnosingMatcher<Vector3D>() {
149 @Override
150 public void describeTo(Description description) {
151 description.appendList("Vector3D[", ", ", "]",
152 Arrays.<SelfDescribing>asList(x, y, z));
153 }
154
155 @Override
156 protected boolean matchesSafely(Vector3D item,
157 Description mismatchDescription) {
158 if (!x.matches(item.getX())) {
159 mismatchDescription.appendText("the x coordinate ");
160 x.describeMismatch(item.getX(), mismatchDescription);
161 return false;
162 }
163 if (!y.matches(item.getY())) {
164 mismatchDescription.appendText("the y coordinate ");
165 y.describeMismatch(item.getY(), mismatchDescription);
166 return false;
167 }
168 if (!z.matches(item.getZ())) {
169 mismatchDescription.appendText("the z coordinate ");
170 z.describeMismatch(item.getZ(), mismatchDescription);
171 return false;
172 }
173 return true;
174 }
175 };
176 }
177
178
179
180
181
182
183
184
185 public static Matcher<Vector3D> vectorCloseTo(Vector3D vector, double absTol) {
186 return vector(closeTo(vector.getX(), absTol),
187 closeTo(vector.getY(), absTol), closeTo(vector.getZ(), absTol));
188 }
189
190
191
192
193
194
195
196
197
198 public static Matcher<Vector3D> vectorCloseTo(Vector3D vector, int ulps) {
199 return vector(relativelyCloseTo(vector.getX(), ulps),
200 relativelyCloseTo(vector.getY(), ulps),
201 relativelyCloseTo(vector.getZ(), ulps));
202 }
203
204
205
206
207
208
209
210
211
212
213 public static Matcher<Vector3D> vectorCloseTo(double x, double y, double z, int ulps) {
214 return vectorCloseTo(new Vector3D(x, y, z), ulps);
215 }
216
217
218
219
220
221
222
223
224
225
226
227 public static Matcher<Vector3D> vectorCloseTo(Vector3D vector,
228 double absTol, int ulps) {
229 return vector(numberCloseTo(vector.getX(), absTol, ulps),
230 numberCloseTo(vector.getY(), absTol, ulps),
231 numberCloseTo(vector.getZ(), absTol, ulps));
232 }
233
234
235
236
237
238
239
240
241 public static Matcher<PVCoordinates> pvIs(
242 final Matcher<? super Vector3D> position,
243 final Matcher<? super Vector3D> velocity) {
244 return new TypeSafeDiagnosingMatcher<PVCoordinates>() {
245
246 @Override
247 public void describeTo(Description description) {
248 description.appendText("position ");
249 description.appendDescriptionOf(position);
250 description.appendText(" and velocity ");
251 description.appendDescriptionOf(velocity);
252 }
253
254 @Override
255 protected boolean matchesSafely(PVCoordinates item,
256 Description mismatchDescription) {
257 if (!position.matches(item.getPosition())) {
258
259 mismatchDescription.appendText("position ");
260 position.describeMismatch(item.getPosition(), mismatchDescription);
261 return false;
262 } else if (!velocity.matches(item.getVelocity())) {
263
264 mismatchDescription.appendText("velocity ");
265 velocity.describeMismatch(item.getVelocity(), mismatchDescription);
266 return false;
267 } else {
268
269 return true;
270 }
271 }
272 };
273 }
274
275
276
277
278
279
280
281 public static Matcher<PVCoordinates> pvIs(PVCoordinates pv) {
282 return pvCloseTo(pv, 0);
283 }
284
285
286
287
288
289
290
291
292
293 public static Matcher<PVCoordinates> pvCloseTo(PVCoordinates pv,
294 double absTol) {
295 return pvIs(vectorCloseTo(pv.getPosition(), absTol),
296 vectorCloseTo(pv.getVelocity(), absTol));
297 }
298
299
300
301
302
303
304
305
306 public static Matcher<PVCoordinates> pvCloseTo(PVCoordinates pv, int ulps) {
307 return pvIs(vectorCloseTo(pv.getPosition(), ulps),
308 vectorCloseTo(pv.getVelocity(), ulps));
309 }
310
311
312
313
314
315
316
317
318
319
320 public static Matcher<Double> relativelyCloseTo(final double expected,
321 final int ulps) {
322 return new TypeSafeDiagnosingMatcher<Double>() {
323
324 @Override
325 public void describeTo(Description description) {
326 description.appendText("a numeric value within ")
327 .appendValue(ulps).appendText(" ulps of ")
328 .appendValue(expected);
329 }
330
331 @Override
332 protected boolean matchesSafely(Double item,
333 Description mismatchDescription) {
334 if (!Precision.equals(item, expected, ulps)) {
335 mismatchDescription
336 .appendValue(item)
337 .appendText(" was off by ")
338 .appendValue(
339 Double.doubleToLongBits(item)
340 - Double.doubleToLongBits(expected))
341 .appendText(" ulps");
342 return false;
343 }
344 return true;
345 }
346 };
347 }
348
349
350
351
352
353
354
355
356
357
358
359
360 public static Matcher<Double> numberCloseTo(double number, double absTol,
361 int ulps) {
362 Collection<Matcher<Double>> matchers = new ArrayList<>(2);
363 matchers.add(closeTo(number, absTol));
364 matchers.add(relativelyCloseTo(number, ulps));
365 return anyOf(matchers);
366 }
367
368
369
370
371
372
373
374
375
376 public static <T> Matcher<T> anyOf(Collection<? extends Matcher<? super T>> matchers) {
377 return new TypeSafeDiagnosingMatcher<T>() {
378 @Override
379 protected boolean matchesSafely(final T item,
380 final Description mismatchDescription) {
381 boolean first = true;
382 for (Matcher<? super T> matcher : matchers) {
383 if (matcher.matches(item)) {
384 return true;
385 } else {
386 if (!first) {
387 mismatchDescription.appendText(" and ");
388 }
389 matcher.describeMismatch(item, mismatchDescription);
390 }
391 first = false;
392 }
393 return false;
394 }
395
396 @Override
397 public void describeTo(final Description description) {
398 description.appendList("(", " or ", ")", matchers);
399 }
400 };
401 }
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417 public static Matcher<Double> closeTo(final double value,
418 final double delta) {
419
420 return new TypeSafeMatcher<Double>() {
421 @Override
422 public boolean matchesSafely(Double item) {
423 return actualDelta(item) <= 0.0;
424 }
425
426 @Override
427 public void describeMismatchSafely(Double item, Description mismatchDescription) {
428 mismatchDescription.appendValue(item)
429 .appendText(" differed by ")
430 .appendValue(actualDelta(item));
431 }
432
433 @Override
434 public void describeTo(Description description) {
435 description.appendText("a numeric value within ")
436 .appendValue(delta)
437 .appendText(" of ")
438 .appendValue(value);
439 }
440
441 private double actualDelta(Double item) {
442 return (FastMath.abs((item - value)) - delta);
443 }
444 };
445
446 }
447
448
449
450
451
452
453
454
455
456
457
458 public static <T extends Comparable<T>> Matcher<T> greaterThan(final T expected) {
459 return new TypeSafeDiagnosingMatcher<T>() {
460 @Override
461 protected boolean matchesSafely(T item, Description mismatchDescription) {
462 if (expected.compareTo(item) >= 0) {
463 mismatchDescription.appendText("less than or equal to ")
464 .appendValue(expected);
465 return false;
466 }
467 return true;
468 }
469
470 @Override
471 public void describeTo(Description description) {
472 description.appendText("greater than ").appendValue(expected);
473 }
474 };
475 }
476
477
478
479
480
481
482
483
484
485
486
487
488 public static Matcher<AbsoluteDate> durationFrom(final AbsoluteDate date,
489 final Matcher<Double> valueMatcher) {
490 return new TypeSafeDiagnosingMatcher<AbsoluteDate>() {
491 @Override
492 public void describeTo(Description description) {
493 description.appendText("delta from ");
494 description.appendValue(date);
495 description.appendText(" is ");
496 description.appendDescriptionOf(valueMatcher);
497 }
498
499 @Override
500 protected boolean matchesSafely(AbsoluteDate item,
501 Description mismatchDescription) {
502 double delta = item.durationFrom(date);
503 boolean matched = valueMatcher.matches(delta);
504 if (!matched) {
505 mismatchDescription.appendText("delta to ");
506 mismatchDescription.appendValue(item);
507 mismatchDescription.appendText(" ");
508 valueMatcher.describeMismatch(delta, mismatchDescription);
509 }
510 return matched;
511 }
512 };
513 }
514
515
516
517
518
519
520
521
522
523
524 public static Matcher<Rotation> distanceIs(final Rotation from,
525 final Matcher<Double> valueMatcher) {
526 return new TypeSafeDiagnosingMatcher<Rotation>() {
527
528 @Override
529 public void describeTo(Description description) {
530 description.appendText("distance from ");
531 description.appendValue(from);
532 description.appendText(" is ");
533 description.appendDescriptionOf(valueMatcher);
534 }
535
536 @Override
537 protected boolean matchesSafely(Rotation item,
538 Description mismatchDescription) {
539 double distance = Rotation.distance(from, item);
540 boolean matched = valueMatcher.matches(distance);
541 if (!matched) {
542 mismatchDescription.appendText("distance to ");
543 mismatchDescription.appendValue(item);
544 mismatchDescription.appendText(" was ");
545 valueMatcher
546 .describeMismatch(distance, mismatchDescription);
547 }
548 return matched;
549 }
550 };
551 }
552
553
554
555
556
557
558
559 public static Matcher<Attitude> attitudeIs(final Attitude expected) {
560 final Matcher<AbsoluteDate> dateMatcher = durationFrom(expected.getDate(), is(0.0));
561 final Matcher<Rotation> rotationMatcher = distanceIs(expected.getRotation(), is(0.0));
562 final Matcher<Vector3D> spinMatcher = vectorCloseTo(expected.getSpin(), 0);
563 final Matcher<Vector3D> accelerationMatcher =
564 vectorCloseTo(expected.getRotationAcceleration(), 0);
565 final Rotation r = expected.getRotation();
566 return new TypeSafeDiagnosingMatcher<Attitude>() {
567
568 @Override
569 protected boolean matchesSafely(Attitude item,
570 final Description mismatchDescription) {
571 item = item.withReferenceFrame(expected.getReferenceFrame());
572 if (!dateMatcher.matches(item)) {
573 mismatchDescription.appendText("date ")
574 .appendDescriptionOf(dateMatcher);
575 }
576 if (!rotationMatcher.matches(item.getRotation())) {
577 mismatchDescription.appendText("rotation ")
578 .appendDescriptionOf(rotationMatcher);
579 return false;
580 }
581 if (!spinMatcher.matches(item.getSpin())) {
582 mismatchDescription.appendText("spin ")
583 .appendDescriptionOf(spinMatcher);
584 return false;
585 }
586 if (!accelerationMatcher.matches(item.getRotationAcceleration())) {
587 mismatchDescription.appendText("rotation acceleration ")
588 .appendDescriptionOf(accelerationMatcher);
589 return false;
590 }
591 return true;
592 }
593
594 @Override
595 public void describeTo(Description description) {
596 description.appendText("attitude on ").appendValue(expected.getDate())
597 .appendText(" rotation ").appendValueList("[", ",", "]", r.getQ0(), r.getQ1(), r.getQ2(), r.getQ3())
598 .appendText(" spin ").appendDescriptionOf(spinMatcher)
599 .appendText(" acceleration ").appendDescriptionOf(accelerationMatcher);
600 }
601 };
602 }
603
604
605 }