1   /* Contributed in the public domain.
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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   * A set of matchers specific to the Orekit value classes.
42   *
43   * @author Evan Ward
44   */
45  public class OrekitMatchers {
46  
47      /**
48       * Match a geodetic point
49       *
50       * @param lat latitude matcher, in radians
51       * @param lon longitude matcher, in radians
52       * @param alt altitude matcher, in meters
53       * @return a {@link GeodeticPoint} matcher
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       * Match a geodetic point
94       *
95       * @param lat latitude, in radians
96       * @param lon longitude, in radians
97       * @param alt altitude, in meters
98       * @return matcher of a {@link GeodeticPoint}
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      * Match a geodetic point by comparing it with another one.
108      *
109      * @param expected the expected value
110      * @param absTol   the absolute tolerance on the comparison, in meters.
111      *                 Differences less than this value will be ignored.
112      * @return a {@link GeodeticPoint} matcher
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      * Match a geodetic point by comparing it with another one.
124      *
125      * @param expected the expected value
126      * @param ulps     the ulps difference allowed
127      * @return a {@link GeodeticPoint} matcher
128      * @see #relativelyCloseTo
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      * Matches a {@link Vector3D} based on its three coordinates.
139      *
140      * @param x matcher for the x coordinate
141      * @param y matcher for the y coordinate
142      * @param z matcher for the z coordinate
143      * @return a vector matcher
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      * Matches a {@link Vector3D} close to another one.
180      *
181      * @param vector the reference vector
182      * @param absTol absolute tolerance of comparison, in each dimension
183      * @return a vector matcher.
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      * Matches a {@link Vector3D} close to another one.
192      *
193      * @param vector the reference vector
194      * @param ulps   the relative tolerance, in units in last place, of the
195      *               Comparison of each dimension.
196      * @return a vector matcher.
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      * Alias for {@link #vectorCloseTo(Vector3D, int)}
206      *
207      * @param x    the x component
208      * @param y    the y component
209      * @param z    the z component
210      * @param ulps the relative tolerance, in ulps
211      * @return a vector matcher
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      * Matches a {@link Vector3D} to another one.
219      *
220      * @param vector the reference vector
221      * @param absTol the absolute tolerance of comparison, in each dimension.
222      * @param ulps   the relative tolerance of comparison in each dimension, in
223      *               units in last place.
224      * @return a matcher that matches if either the absolute or relative
225      * comparison matches in each dimension.
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      * Match a {@link PVCoordinates}
236      *
237      * @param position matcher for the position
238      * @param velocity matcher for the velocity
239      * @return a matcher of {@link PVCoordinates}
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                     // position doesn't match
259                     mismatchDescription.appendText("position ");
260                     position.describeMismatch(item.getPosition(), mismatchDescription);
261                     return false;
262                 } else if (!velocity.matches(item.getVelocity())) {
263                     // velocity doesn't match
264                     mismatchDescription.appendText("velocity ");
265                     velocity.describeMismatch(item.getVelocity(), mismatchDescription);
266                     return false;
267                 } else {
268                     // both p and v matched
269                     return true;
270                 }
271             }
272         };
273     }
274 
275     /**
276      * Check that a {@link PVCoordinates} is the same as another one.
277      *
278      * @param pv the reference {@link PVCoordinates}
279      * @return a {@link PVCoordinates} {@link Matcher}
280      */
281     public static Matcher<PVCoordinates> pvIs(PVCoordinates pv) {
282         return pvCloseTo(pv, 0);
283     }
284 
285     /**
286      * Match a {@link PVCoordinates} close to another one.
287      *
288      * @param pv     the reference {@link PVCoordinates}
289      * @param absTol distance a matched {@link PVCoordinates} can be from the reference in
290      *               any one coordinate.
291      * @return a matcher of {@link PVCoordinates}.
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      * Match a {@link PVCoordinates} close to another one.
301      *
302      * @param pv   the reference {@link PVCoordinates}
303      * @param ulps the units in last place any coordinate can be off by.
304      * @return a matcher of {@link PVCoordinates}.
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      * Checks if two numbers are relatively close to each other. For absolute
313      * comparisons, see {@link #closeTo(double, double)}.
314      *
315      * @param expected the expected value in the relative comparison
316      * @param ulps     the units in last place of {@code expected} the two
317      *                 numbers can be off by.
318      * @return a matcher of numbers
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      * Check a number is close to another number using a relative
351      * <strong>or</strong> absolute comparison.
352      *
353      * @param number the expected value
354      * @param absTol absolute tolerance of comparison
355      * @param ulps   units in last place tolerance for relative comparison
356      * @return a matcher that matches if the differences is less than or equal
357      * to absTol <strong>or</strong> the two numbers differ by less or equal
358      * ulps.
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      * Create a matcher that matches if at least one of the given matchers match. Gives
370      * better descriptions that {@link org.hamcrest.CoreMatchers#anyOf(Iterable)}.
371      *
372      * @param matchers to try.
373      * @param <T>      type of object to match.
374      * @return a new matcher.
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     /* Copid from Hamcrest's IsCloseTo under the new BSD license.
404      * Copyright (c) 2000-2006 hamcrest.org
405      */
406 
407     /**
408      * Creates a matcher of {@link Double}s that matches when an examined double
409      * is equal to the specified <code>operand</code>, within a range of +/-
410      * <code>error</code>. <p/> For example:
411      * <pre>assertThat(1.03, is(closeTo(1.0, 0.03)))</pre>
412      *
413      * @param value the expected value of matching doubles
414      * @param delta the delta (+/-) within which matches will be allowed
415      * @return a double matcher.
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     /* Replace with Matchers.greaterThan(...) if hamcrest becomes available. */
449 
450     /**
451      * Create a matcher to see if a value is greater than another one using {@link
452      * Comparable#compareTo(Object)}.
453      *
454      * @param expected value.
455      * @param <T>      type of value.
456      * @return matcher of value.
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      * Matcher for the distance in seconds between two {@link AbsoluteDate}s. Uses {@link
480      * AbsoluteDate#durationFrom(AbsoluteDate)}.
481      *
482      * @param date         the date to compare with.
483      * @param valueMatcher the matcher for the delta. For example, {@code closeTo(0,
484      *                     1e-10)}.
485      * @return a matcher that checks the time difference between two {@link
486      * AbsoluteDate}s.
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      * Matcher that compares to {@link Rotation}s using {@link Rotation#distance(Rotation,
517      * Rotation)}.
518      *
519      * @param from         one of the rotations to compare
520      * @param valueMatcher matcher for the distances. For example {@code closeTo(0,
521      *                     1e-10)}.
522      * @return a matcher for rotations.
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      * Check that two attitudes are equivalent.
555      *
556      * @param expected attitude.
557      * @return attitude matcher.
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 }