/* Contributed in the public domain.
* Licensed to CS Systèmes d'Information (CS) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* CS licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.orekit;
import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.hipparchus.util.FastMath;
import org.hipparchus.util.Precision;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.SelfDescribing;
import org.hamcrest.TypeSafeDiagnosingMatcher;
import org.hamcrest.TypeSafeMatcher;
import org.orekit.bodies.GeodeticPoint;
import org.orekit.utils.Constants;
import org.orekit.utils.PVCoordinates;
import java.util.Arrays;
import static org.hamcrest.CoreMatchers.either;
import static org.hamcrest.CoreMatchers.is;
/**
* A set of matchers specific to the Orekit value classes.
*
* @author Evan Ward
*/
public class OrekitMatchers {
/**
* Match a geodetic point
*
* @param lat latitude matcher, in radians
* @param lon longitude matcher, in radians
* @param alt altitude matcher, in meters
* @return a {@link GeodeticPoint} matcher
*/
public static Matcher<GeodeticPoint> geodeticPoint(
final Matcher<Double> lat,
final Matcher<Double> lon,
final Matcher<Double> alt) {
return new TypeSafeDiagnosingMatcher<GeodeticPoint>() {
@Override
public void describeTo(Description description) {
description.appendList("GeodeticPoint[", ",", "]",
Arrays.<SelfDescribing>asList(lat, lon, alt));
}
@Override
protected boolean matchesSafely(GeodeticPoint item,
Description mismatchDescription) {
if (!lat.matches(item.getLatitude())) {
mismatchDescription.appendText("the latitude ");
lat.describeMismatch(item.getLatitude(),
mismatchDescription);
return false;
}
if (!lon.matches(item.getLongitude())) {
mismatchDescription.appendText("the longitude ");
lon.describeMismatch(item.getLongitude(),
mismatchDescription);
return false;
}
if (!alt.matches(item.getAltitude())) {
mismatchDescription.appendText("the altitude ");
alt.describeMismatch(item.getAltitude(),
mismatchDescription);
return false;
}
return true;
}
};
}
/**
* Match a geodetic point
*
* @param lat latitude, in radians
* @param lon longitude, in radians
* @param alt altitude, in meters
* @return matcher of a {@link GeodeticPoint}
*/
public static Matcher<GeodeticPoint> geodeticPoint(double lat,
double lon,
double alt) {
return geodeticPoint(is(lat), is(lon), is(alt));
}
/**
* Match a geodetic point by comparing it with another one.
*
* @param expected the expected value
* @param absTol the absolute tolerance on the comparison, in meters.
* Differences less than this value will be ignored.
* @return a {@link GeodeticPoint} matcher
*/
public static Matcher<GeodeticPoint> geodeticPointCloseTo(
GeodeticPoint expected, double absTol) {
double angularAbsTol = absTol / Constants.WGS84_EARTH_EQUATORIAL_RADIUS;
return geodeticPoint(closeTo(expected.getLatitude(), angularAbsTol),
closeTo(expected.getLongitude(), angularAbsTol),
closeTo(expected.getAltitude(), absTol));
}
/**
* Match a geodetic point by comparing it with another one.
*
* @param expected the expected value
* @param ulps the ulps difference allowed
* @return a {@link GeodeticPoint} matcher
* @see #relativelyCloseTo
*/
public static Matcher<GeodeticPoint> geodeticPointCloseTo(
GeodeticPoint expected, int ulps) {
return geodeticPoint(relativelyCloseTo(expected.getLatitude(), ulps),
relativelyCloseTo(expected.getLongitude(), ulps),
relativelyCloseTo(expected.getAltitude(), ulps));
}
/**
* Matches a {@link Vector3D} based on its three coordinates.
*
* @param x matcher for the x coordinate
* @param y matcher for the y coordinate
* @param z matcher for the z coordinate
* @return a vector matcher
*/
public static Matcher<Vector3D> vector(final Matcher<Double> x,
final Matcher<Double> y,
final Matcher<Double> z) {
return new TypeSafeDiagnosingMatcher<Vector3D>() {
@Override
public void describeTo(Description description) {
description.appendList("Vector3D[", ",", "]",
Arrays.<SelfDescribing>asList(x, y, z));
}
@Override
protected boolean matchesSafely(Vector3D item,
Description mismatchDescription) {
if (!x.matches(item.getX())) {
mismatchDescription.appendText("the x coordinate ");
x.describeMismatch(item.getX(), mismatchDescription);
return false;
}
if (!y.matches(item.getY())) {
mismatchDescription.appendText("the y coordinate ");
y.describeMismatch(item.getY(), mismatchDescription);
return false;
}
if (!z.matches(item.getZ())) {
mismatchDescription.appendText("the z coordinate ");
z.describeMismatch(item.getZ(), mismatchDescription);
return false;
}
return true;
}
};
}
/**
* Matches a {@link Vector3D} close to another one.
*
* @param vector the reference vector
* @param absTol absolute tolerance of comparison, in each dimension
* @return a vector matcher.
*/
public static Matcher<Vector3D> vectorCloseTo(Vector3D vector, double absTol) {
return vector(closeTo(vector.getX(), absTol),
closeTo(vector.getY(), absTol), closeTo(vector.getZ(), absTol));
}
/**
* Matches a {@link Vector3D} close to another one.
*
* @param vector the reference vector
* @param ulps the relative tolerance, in units in last place, of the
* Comparison of each dimension.
* @return a vector matcher.
*/
public static Matcher<Vector3D> vectorCloseTo(Vector3D vector, int ulps) {
return vector(relativelyCloseTo(vector.getX(), ulps),
relativelyCloseTo(vector.getY(), ulps),
relativelyCloseTo(vector.getZ(), ulps));
}
/**
* Alias for {@link #vectorCloseTo(Vector3D, int)}
*
* @param x the x component
* @param y the y component
* @param z the z component
* @param ulps the relative tolerance, in ulps
* @return a vector matcher
*/
public static Matcher<Vector3D> vectorCloseTo(double x, double y, double z, int ulps) {
return vectorCloseTo(new Vector3D(x, y, z), ulps);
}
/**
* Matches a {@link Vector3D} to another one.
*
* @param vector the reference vector
* @param absTol the absolute tolerance of comparison, in each dimension.
* @param ulps the relative tolerance of comparison in each dimension, in
* units in last place.
* @return a matcher that matches if either the absolute or relative
* comparison matches in each dimension.
*/
public static Matcher<Vector3D> vectorCloseTo(Vector3D vector,
double absTol, int ulps) {
return vector(numberCloseTo(vector.getX(), absTol, ulps),
numberCloseTo(vector.getY(), absTol, ulps),
numberCloseTo(vector.getZ(), absTol, ulps));
}
/**
* Match a {@link PVCoordinates}
*
* @param position matcher for the position
* @param velocity matcher for the velocity
* @return a matcher of {@link PVCoordinates}
*/
public static Matcher<PVCoordinates> pvIs(
final Matcher<? super Vector3D> position,
final Matcher<? super Vector3D> velocity) {
return new TypeSafeDiagnosingMatcher<PVCoordinates>() {
@Override
public void describeTo(Description description) {
description.appendText("position ");
description.appendDescriptionOf(position);
description.appendText(" and velocity ");
description.appendDescriptionOf(velocity);
}
@Override
protected boolean matchesSafely(PVCoordinates item,
Description mismatchDescription) {
if (!position.matches(item.getPosition())) {
// position doesn't match
mismatchDescription.appendText("position ");
position.describeMismatch(item.getPosition(), mismatchDescription);
return false;
} else if (!velocity.matches(item.getVelocity())) {
// velocity doesn't match
mismatchDescription.appendText("velocity ");
velocity.describeMismatch(item.getVelocity(), mismatchDescription);
return false;
} else {
// both p and v matched
return true;
}
}
};
}
/**
* Check that a {@link PVCoordinates} is the same as another one.
*
* @param pv the reference {@link PVCoordinates}
* @return a {@link PVCoordinates} {@link Matcher}
*/
public static Matcher<PVCoordinates> pvIs(PVCoordinates pv) {
return pvCloseTo(pv, 0);
}
/**
* Match a {@link PVCoordinates} close to another one.
*
* @param pv the reference {@link PVCoordinates}
* @param absTol distance a matched {@link PVCoordinates} can be from the reference in
* any one coordinate.
* @return a matcher of {@link PVCoordinates}.
*/
public static Matcher<PVCoordinates> pvCloseTo(PVCoordinates pv,
double absTol) {
return pvIs(vectorCloseTo(pv.getPosition(), absTol),
vectorCloseTo(pv.getVelocity(), absTol));
}
/**
* Match a {@link PVCoordinates} close to another one.
*
* @param pv the reference {@link PVCoordinates}
* @param ulps the units in last place any coordinate can be off by.
* @return a matcher of {@link PVCoordinates}.
*/
public static Matcher<PVCoordinates> pvCloseTo(PVCoordinates pv, int ulps) {
return pvIs(vectorCloseTo(pv.getPosition(), ulps),
vectorCloseTo(pv.getVelocity(), ulps));
}
/**
* Checks if two numbers are relatively close to each other. For absolute
* comparisons, see {@link #closeTo(double, double)}.
*
* @param expected the expected value in the relative comparison
* @param ulps the units in last place of {@code expected} the two
* numbers can be off by.
* @return a matcher of numbers
*/
public static Matcher<Double> relativelyCloseTo(final double expected,
final int ulps) {
return new TypeSafeDiagnosingMatcher<Double>() {
@Override
public void describeTo(Description description) {
description.appendText("a numeric value within ")
.appendValue(ulps).appendText(" ulps of ")
.appendValue(expected);
}
@Override
protected boolean matchesSafely(Double item,
Description mismatchDescription) {
if (!Precision.equals(item, expected, ulps)) {
mismatchDescription
.appendValue(item)
.appendText(" was off by ")
.appendValue(
Double.doubleToLongBits(item)
- Double.doubleToLongBits(expected))
.appendText(" ulps");
return false;
}
return true;
}
};
}
/**
* Check a number is close to another number using a relative
* <strong>or</strong> absolute comparison.
*
* @param number the expected value
* @param absTol absolute tolerance of comparison
* @param ulps units in last place tolerance for relative comparison
* @return a matcher that matches if the differences is less than or equal
* to absTol <strong>or</strong> the two numbers differ by less or equal
* ulps.
*/
public static Matcher<Double> numberCloseTo(double number, double absTol,
int ulps) {
return either(closeTo(number, absTol)).or(
relativelyCloseTo(number, ulps));
}
/* Copid from Hamcrest's IsCloseTo under the new BSD license.
* Copyright (c) 2000-2006 hamcrest.org
*/
/**
* Creates a matcher of {@link Double}s that matches when an examined double
* is equal to the specified <code>operand</code>, within a range of +/-
* <code>error</code>. <p/> For example:
* <pre>assertThat(1.03, is(closeTo(1.0, 0.03)))</pre>
*
* @param value the expected value of matching doubles
* @param delta the delta (+/-) within which matches will be allowed
* @return a double matcher.
*/
public static Matcher<Double> closeTo(final double value,
final double delta) {
return new TypeSafeMatcher<Double>() {
@Override
public boolean matchesSafely(Double item) {
return actualDelta(item) <= 0.0;
}
@Override
public void describeMismatchSafely(Double item, Description mismatchDescription) {
mismatchDescription.appendValue(item)
.appendText(" differed by ")
.appendValue(actualDelta(item));
}
@Override
public void describeTo(Description description) {
description.appendText("a numeric value within ")
.appendValue(delta)
.appendText(" of ")
.appendValue(value);
}
private double actualDelta(Double item) {
return (FastMath.abs((item - value)) - delta);
}
};
}
}