Quaternion mix is buggy - math

I'm doing Quaternion mixing via SLERP, but my implementation does something very buggy. You can see it on this video: video
Here is the code:
cosTheta = quatDot(a, b);
if (cosTheta > 0.9){
var quat = new Vec4(
a.x + (b.x - a.x)*t,
a.y + (b.y - a.y)*t,
a.z + (b.z - a.z)*t,
a.w + (b.w - a.w)*t
);
normalizeQuat(quat);
return quat;
}
cosTheta = Math.min(Math.max(cosTheta, -1), 1); //clamp
var theta = Math.acos(cosTheta) * t;
var v2 = quatMinus(b, quatExtend(a, cosTheta));
normalizeQuat(v2);
return quatSum(quatExtend(a, cosTheta), quatExtend(v2, Math.sin(theta)));
Here I have my help functions:
function quatDot(a,b){
return a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w;
return a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w;
}
function quatMultiply(q,p){
return new Vec4(
q.w * p.x + q.x * p.w + q.y * p.z - q.z * p.y,
q.w * p.y + q.y * p.w + q.z * p.x - q.x * p.z,
q.w * p.z + q.z * p.w + q.x * p.y - q.y * p.x,
q.w * p.w - q.x * p.x - q.y * p.y - q.z * p.z);
}
function quatExtend(q, t){
return new Vec4(
q.x * t, q.y * t, q.z * t, q.w * t);
}
function quatMinus(q, p){
return new Vec4(
q.x - p.x,
q.y - p.y,
q.z - p.z,
q.w - p.w
);
}
function quatSum(q, p){
return new Vec4(
q.x + p.x,
q.y + p.y,
q.z + p.z,
q.w + p.w
);
}
I tried many implementations from websites but there is always buggy movement. When I tried simple linear interpolation, then animation is smooth, but weird accelerating.

Here's a easy bug: if cosTheta < 0.9 and t= 0, then theta = 0, which means Math.sin(theta) = 0, which means quatExtend(v2, Math.sin(theta)) is the zero quaternion, and you end up returning quatExtend(a, cosTheta), which is not a unit quaternion.
I would expect a correct implementation to take the inverse (or conjugate) of one of the quaternions at least once, and never to call normalizeQuat. But that's just intuition. To get past intuition, try writing a unit test!
By the way, if we're doing a code review, it's confusing that cosTheta is not the cosine of theta. Also, the if (cosTheta > 0.9) block smells like premature optimization; you can probably get rid of it. :)

Related

Intersection between two vectors

Is there efficient way to find intersection between two vectors?
Ray is infinite so one way is to turn one vector to ray, find intersection between that ray and other vector. Then if there is intersection, find distance between intersection and first vector.
If it is 0 then there is intersection between two vectors, if i don't miss something.
But is there better way in three.js?
That is more a math question than a three.js one, as you mentioned you could use existing methods on the Ray object and work your way out from there, but I doubt this would be optimal as you are concerned about performance.
The right approach is probably to determine the shortest distance between two segments and if this distance is zero then they intersect. You can find a similar discussion on that thread: The Algorithm to Find the Point of Intersection of Two 3D Line Segment
And here I found a C algorithm that does exactly that, it should translate to Javascript without much efforts:
typedef struct {
double x,y,z;
} XYZ;
/*
Calculate the line segment PaPb that is the shortest route between
two lines P1P2 and P3P4. Calculate also the values of mua and mub where
Pa = P1 + mua (P2 - P1)
Pb = P3 + mub (P4 - P3)
Return FALSE if no solution exists.
*/
int LineLineIntersect(
XYZ p1,XYZ p2,XYZ p3,XYZ p4,XYZ *pa,XYZ *pb,
double *mua, double *mub)
{
XYZ p13,p43,p21;
double d1343,d4321,d1321,d4343,d2121;
double numer,denom;
p13.x = p1.x - p3.x;
p13.y = p1.y - p3.y;
p13.z = p1.z - p3.z;
p43.x = p4.x - p3.x;
p43.y = p4.y - p3.y;
p43.z = p4.z - p3.z;
if (ABS(p43.x) < EPS && ABS(p43.y) < EPS && ABS(p43.z) < EPS)
return(FALSE);
p21.x = p2.x - p1.x;
p21.y = p2.y - p1.y;
p21.z = p2.z - p1.z;
if (ABS(p21.x) < EPS && ABS(p21.y) < EPS && ABS(p21.z) < EPS)
return(FALSE);
d1343 = p13.x * p43.x + p13.y * p43.y + p13.z * p43.z;
d4321 = p43.x * p21.x + p43.y * p21.y + p43.z * p21.z;
d1321 = p13.x * p21.x + p13.y * p21.y + p13.z * p21.z;
d4343 = p43.x * p43.x + p43.y * p43.y + p43.z * p43.z;
d2121 = p21.x * p21.x + p21.y * p21.y + p21.z * p21.z;
denom = d2121 * d4343 - d4321 * d4321;
if (ABS(denom) < EPS)
return(FALSE);
numer = d1343 * d4321 - d1321 * d4343;
*mua = numer / denom;
*mub = (d1343 + d4321 * (*mua)) / d4343;
pa->x = p1.x + *mua * p21.x;
pa->y = p1.y + *mua * p21.y;
pa->z = p1.z + *mua * p21.z;
pb->x = p3.x + *mub * p43.x;
pb->y = p3.y + *mub * p43.y;
pb->z = p3.z + *mub * p43.z;
return(TRUE);
}

How to optimize this example code to run quicker?

I am currently in the process of learning about, and making a PID controller. I first wrote a program to take data from the accelerometer, and was able to get raw data, but I had no idea on how to write a proper filter to convert the raw data to degrees.
I found an example code with libraries that had a "Quaternian filter" that outputs the raw data in degrees, but it refreshes way too slow to be useful. I did not see any delays in the main code, is there any way I can make it refresh faster?
Here is the main code:
#include <Servo.h>
int myRoll = 0;
/* MPU9250 Basic Example Code
by: Kris Winer
date: April 1, 2014
license: Beerware - Use this code however you'd like. If you
find it useful you can buy me a beer some time.
Modified by Brent Wilkins July 19, 2016
Demonstrate basic MPU-9250 functionality including parameterizing the register
addresses, initializing the sensor, getting properly scaled accelerometer,
gyroscope, and magnetometer data out. Added display functions to allow display
to on breadboard monitor. Addition of 9 DoF sensor fusion using open source
Madgwick and Mahony filter algorithms. Sketch runs on the 3.3 V 8 MHz Pro Mini
and the Teensy 3.1.
SDA and SCL should have external pull-up resistors (to 3.3V).
10k resistors are on the EMSENSR-9250 breakout board.
Hardware setup:
MPU9250 Breakout --------- Arduino
VDD ---------------------- 3.3V
VDDI --------------------- 3.3V
SDA ----------------------- A4
SCL ----------------------- A5
GND ---------------------- GND
*/
#include "quaternionFilters.h"
#include "MPU9250.h"
#define AHRS true // Set to false for basic data read
#define SerialDebug true // Set to true to get Serial output for debugging
// Pin definitions
int intPin = 12; // These can be changed, 2 and 3 are the Arduinos ext int pins
int myLed = 13; // Set up pin 13 led for toggling
Servo myservo;
MPU9250 myIMU;
void setup()
{
myservo.attach(9);
myservo.write(90);
Wire.begin();
// TWBR = 12; // 400 kbit/sec I2C speed
Serial.begin(115200);
// Set up the interrupt pin, its set as active high, push-pull
pinMode(intPin, INPUT);
digitalWrite(intPin, LOW);
pinMode(myLed, OUTPUT);
digitalWrite(myLed, HIGH);
// Read the WHO_AM_I register, this is a good test of communication
byte c = myIMU.readByte(MPU9250_ADDRESS, WHO_AM_I_MPU9250);
Serial.print("MPU9250 "); Serial.print("I AM "); Serial.print(c, HEX);
Serial.print(" I should be "); Serial.println(0x71, HEX);
if (c == 0x73) // WHO_AM_I should always be 0x68
{
Serial.println("MPU9250 is online...");
/*
// Start by performing self test and reporting values
myIMU.MPU9250SelfTest(myIMU.SelfTest);
Serial.print("x-axis self test: acceleration trim within : ");
Serial.print(myIMU.SelfTest[0],1); Serial.println("% of factory value");
Serial.print("y-axis self test: acceleration trim within : ");
Serial.print(myIMU.SelfTest[1],1); Serial.println("% of factory value");
Serial.print("z-axis self test: acceleration trim within : ");
Serial.print(myIMU.SelfTest[2],1); Serial.println("% of factory value");
Serial.print("x-axis self test: gyration trim within : ");
Serial.print(myIMU.SelfTest[3],1); Serial.println("% of factory value");
Serial.print("y-axis self test: gyration trim within : ");
Serial.print(myIMU.SelfTest[4],1); Serial.println("% of factory value");
Serial.print("z-axis self test: gyration trim within : ");
Serial.print(myIMU.SelfTest[5],1); Serial.println("% of factory value");
*/
/* // Calibrate gyro and accelerometers, load biases in bias registers
// myIMU.calibrateMPU9250(myIMU.gyroBias, myIMU.accelBias);
*/
myIMU.initMPU9250();
byte d = myIMU.readByte(AK8963_ADDRESS, WHO_AM_I_AK8963);
/*
// Get magnetometer calibration from AK8963 ROM
// myIMU.initAK8963(myIMU.magCalibration);
// Initialize device for active mode read of magnetometer
*/
} /* if (c == 0x71)
else
{
Serial.print("Could not connect to MPU9250: 0x");
Serial.println(c, HEX);
while(1) ; // Loop forever if communication doesn't happen
}
}
*/
}
void loop(){
// If intPin goes high, all data registers have new data
// On interrupt, check if data ready interrupt
if (myIMU.readByte(MPU9250_ADDRESS, INT_STATUS) & 0x01)
{
myIMU.readAccelData(myIMU.accelCount); // Read the x/y/z adc values
myIMU.getAres();
// Now we'll calculate the accleration value into actual g's
// This depends on scale being set
myIMU.ax = (float)myIMU.accelCount[0]*myIMU.aRes; // - accelBias[0];
myIMU.ay = (float)myIMU.accelCount[1]*myIMU.aRes; // - accelBias[1];
myIMU.az = (float)myIMU.accelCount[2]*myIMU.aRes; // - accelBias[2];
myIMU.readGyroData(myIMU.gyroCount); // Read the x/y/z adc values
myIMU.getGres();
// Calculate the gyro value into actual degrees per second
// This depends on scale being set
myIMU.gx = (float)myIMU.gyroCount[0]*myIMU.gRes;
myIMU.gy = (float)myIMU.gyroCount[1]*myIMU.gRes;
myIMU.gz = (float)myIMU.gyroCount[2]*myIMU.gRes;
myIMU.readMagData(myIMU.magCount); // Read the x/y/z adc values
myIMU.getMres();
// User environmental x-axis correction in milliGauss, should be
// automatically calculated
myIMU.magbias[0] = +470.;
// User environmental x-axis correction in milliGauss TODO axis??
myIMU.magbias[1] = +120.;
// User environmental x-axis correction in milliGauss
myIMU.magbias[2] = +125.;
// Calculate the magnetometer values in milliGauss
// Include factory calibration per data sheet and user environmental
// corrections
// Get actual magnetometer value, this depends on scale being set
myIMU.mx = (float)myIMU.magCount[0]*myIMU.mRes*myIMU.magCalibration[0] -
myIMU.magbias[0];
myIMU.my = (float)myIMU.magCount[1]*myIMU.mRes*myIMU.magCalibration[1] -
myIMU.magbias[1];
myIMU.mz = (float)myIMU.magCount[2]*myIMU.mRes*myIMU.magCalibration[2] -
myIMU.magbias[2];
} // if (readByte(MPU9250_ADDRESS, INT_STATUS) & 0x01)
// Must be called before updating quaternions!
myIMU.updateTime();
// Sensors x (y)-axis of the accelerometer is aligned with the y (x)-axis of
// the magnetometer; the magnetometer z-axis (+ down) is opposite to z-axis
// (+ up) of accelerometer and gyro! We have to make some allowance for this
// orientationmismatch in feeding the output to the quaternion filter. For the
// MPU-9250, we have chosen a magnetic rotation that keeps the sensor forward
// along the x-axis just like in the LSM9DS0 sensor. This rotation can be
// modified to allow any convenient orientation convention. This is ok by
// aircraft orientation standards! Pass gyro rate as rad/s
// MadgwickQuaternionUpdate(ax, ay, az, gx*PI/180.0f, gy*PI/180.0f, gz*PI/180.0f, my, mx, mz);
MahonyQuaternionUpdate(myIMU.ax, myIMU.ay, myIMU.az, myIMU.gx*DEG_TO_RAD,
myIMU.gy*DEG_TO_RAD, myIMU.gz*DEG_TO_RAD, myIMU.my,
myIMU.mx, myIMU.mz, myIMU.deltat);
if (!AHRS)
{
myIMU.delt_t = millis() - myIMU.count;
if (myIMU.delt_t > 500)
{
myIMU.count = millis();
digitalWrite(myLed, !digitalRead(myLed)); // toggle led
} // if (myIMU.delt_t > 500)
} // if (!AHRS)
else
{
// Serial print and/or display at 0.5 s rate independent of data rates
myIMU.delt_t = millis() - myIMU.count;
// update LCD once per half-second independent of read rate
if (myIMU.delt_t > 500)
{
// Define output variables from updated quaternion---these are Tait-Bryan
// angles, commonly used in aircraft orientation. In this coordinate system,
// the positive z-axis is down toward Earth. Yaw is the angle between Sensor
// x-axis and Earth magnetic North (or true North if corrected for local
// declination, looking down on the sensor positive yaw is counterclockwise.
// Pitch is angle between sensor x-axis and Earth ground plane, toward the
// Earth is positive, up toward the sky is negative. Roll is angle between
// sensor y-axis and Earth ground plane, y-axis up is positive roll. These
// arise from the definition of the homogeneous rotation matrix constructed
// from quaternions. Tait-Bryan angles as well as Euler angles are
// non-commutative; that is, the get the correct orientation the rotations
// must be applied in the correct order which for this configuration is yaw,
// pitch, and then roll.
// For more see
// http://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles
// which has additional links.
myIMU.yaw = atan2(2.0f * (*(getQ()+1) * *(getQ()+2) + *getQ() *
*(getQ()+3)), *getQ() * *getQ() + *(getQ()+1) * *(getQ()+1)
- *(getQ()+2) * *(getQ()+2) - *(getQ()+3) * *(getQ()+3));
myIMU.pitch = -asin(2.0f * (*(getQ()+1) * *(getQ()+3) - *getQ() *
*(getQ()+2)));
myIMU.roll = atan2(2.0f * (*getQg() * *(getQ()+1) + *(getQ()+2) *
*(getQ()+3)), *getQ() * *getQ() - *(getQ()+1) * *(getQ()+1)
- *(getQ()+2) * *(getQ()+2) + *(getQ()+3) * *(getQ()+3));
myIMU.pitch *= RAD_TO_DEG;
myIMU.yaw *= RAD_TO_DEG;
// Declination of SparkFun Electronics (40°05'26.6"N 105°11'05.9"W) is
// 8° 30' E ± 0° 21' (or 8.5°) on 2016-07-19
// - http://www.ngdc.noaa.gov/geomag-web/#declination
myIMU.yaw -= 8.5;
myIMU.roll *= RAD_TO_DEG;
if(SerialDebug)
{
Serial.print(myIMU.yaw, 2);
Serial.print(", ");
Serial.print(myIMU.pitch, 2);
Serial.print(", ");
Serial.println(myIMU.roll, 2);
}
myIMU.count = millis();
myIMU.sumCount = 0;
myIMU.sum = 0;
} // if (myIMU.delt_t > 500)
} // if (AHRS)
myservo.write(myIMU.roll);
}
The libraries are below:
here is quaternionFilters.cpp
// Implementation of Sebastian Madgwick's "...efficient orientation filter
// for... inertial/magnetic sensor arrays"
// (see http://www.x-io.co.uk/category/open-source/ for examples & more details)
// which fuses acceleration, rotation rate, and magnetic moments to produce a
// quaternion-based estimate of absolute device orientation -- which can be
// converted to yaw, pitch, and roll. Useful for stabilizing quadcopters, etc.
// The performance of the orientation filter is at least as good as conventional
// Kalman-based filtering algorithms but is much less computationally
// intensive---it can be performed on a 3.3 V Pro Mini operating at 8 MHz!
#include "quaternionFilters.h"
// These are the free parameters in the Mahony filter and fusion scheme, Kp
// for proportional feedback, Ki for integral
#define Kp 2.0f * 5.0f
#define Ki 0.0f
static float GyroMeasError = PI * (40.0f / 180.0f);
// gyroscope measurement drift in rad/s/s (start at 0.0 deg/s/s)
static float GyroMeasDrift = PI * (0.0f / 180.0f);
// There is a tradeoff in the beta parameter between accuracy and response
// speed. In the original Madgwick study, beta of 0.041 (corresponding to
// GyroMeasError of 2.7 degrees/s) was found to give optimal accuracy.
// However, with this value, the LSM9SD0 response time is about 10 seconds
// to a stable initial quaternion. Subsequent changes also require a
// longish lag time to a stable output, not fast enough for a quadcopter or
// robot car! By increasing beta (GyroMeasError) by about a factor of
// fifteen, the response time constant is reduced to ~2 sec. I haven't
// noticed any reduction in solution accuracy. This is essentially the I
// coefficient in a PID control sense; the bigger the feedback coefficient,
// the faster the solution converges, usually at the expense of accuracy.
// In any case, this is the free parameter in the Madgwick filtering and
// fusion scheme.
static float beta = sqrt(3.0f / 4.0f) * GyroMeasError; // Compute beta
// Compute zeta, the other free parameter in the Madgwick scheme usually
// set to a small or zero value
static float zeta = sqrt(3.0f / 4.0f) * GyroMeasDrift;
// Vector to hold integral error for Mahony method
static float eInt[3] = {0.0f, 0.0f, 0.0f};
// Vector to hold quaternion
static float q[4] = {1.0f, 0.0f, 0.0f, 0.0f};
void MadgwickQuaternionUpdate(float ax, float ay, float az, float gx, float gy, float gz, float mx, float my, float mz, float deltat)
{
// short name local variable for readability
float q1 = q[0], q2 = q[1], q3 = q[2], q4 = q[3];
float norm;
float hx, hy, _2bx, _2bz;
float s1, s2, s3, s4;
float qDot1, qDot2, qDot3, qDot4;
// Auxiliary variables to avoid repeated arithmetic
float _2q1mx;
float _2q1my;
float _2q1mz;
float _2q2mx;
float _4bx;
float _4bz;
float _2q1 = 2.0f * q1;
float _2q2 = 2.0f * q2;
float _2q3 = 2.0f * q3;
float _2q4 = 2.0f * q4;
float _2q1q3 = 2.0f * q1 * q3;
float _2q3q4 = 2.0f * q3 * q4;
float q1q1 = q1 * q1;
float q1q2 = q1 * q2;
float q1q3 = q1 * q3;
float q1q4 = q1 * q4;
float q2q2 = q2 * q2;
float q2q3 = q2 * q3;
float q2q4 = q2 * q4;
float q3q3 = q3 * q3;
float q3q4 = q3 * q4;
float q4q4 = q4 * q4;
// Normalise accelerometer measurement
norm = sqrt(ax * ax + ay * ay + az * az);
if (norm == 0.0f) return; // handle NaN
norm = 1.0f/norm;
ax *= norm;
ay *= norm;
az *= norm;
// Normalise magnetometer measurement
norm = sqrt(mx * mx + my * my + mz * mz);
if (norm == 0.0f) return; // handle NaN
norm = 1.0f/norm;
mx *= norm;
my *= norm;
mz *= norm;
// Reference direction of Earth's magnetic field
_2q1mx = 2.0f * q1 * mx;
_2q1my = 2.0f * q1 * my;
_2q1mz = 2.0f * q1 * mz;
_2q2mx = 2.0f * q2 * mx;
hx = mx * q1q1 - _2q1my * q4 + _2q1mz * q3 + mx * q2q2 + _2q2 * my * q3 +
_2q2 * mz * q4 - mx * q3q3 - mx * q4q4;
hy = _2q1mx * q4 + my * q1q1 - _2q1mz * q2 + _2q2mx * q3 - my * q2q2 + my * q3q3 + _2q3 * mz * q4 - my * q4q4;
_2bx = sqrt(hx * hx + hy * hy);
_2bz = -_2q1mx * q3 + _2q1my * q2 + mz * q1q1 + _2q2mx * q4 - mz * q2q2 + _2q3 * my * q4 - mz * q3q3 + mz * q4q4;
_4bx = 2.0f * _2bx;
_4bz = 2.0f * _2bz;
// Gradient decent algorithm corrective step
s1 = -_2q3 * (2.0f * q2q4 - _2q1q3 - ax) + _2q2 * (2.0f * q1q2 + _2q3q4 - ay) - _2bz * q3 * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (-_2bx * q4 + _2bz * q2) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + _2bx * q3 * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz);
s2 = _2q4 * (2.0f * q2q4 - _2q1q3 - ax) + _2q1 * (2.0f * q1q2 + _2q3q4 - ay) - 4.0f * q2 * (1.0f - 2.0f * q2q2 - 2.0f * q3q3 - az) + _2bz * q4 * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (_2bx * q3 + _2bz * q1) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + (_2bx * q4 - _4bz * q2) * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz);
s3 = -_2q1 * (2.0f * q2q4 - _2q1q3 - ax) + _2q4 * (2.0f * q1q2 + _2q3q4 - ay) - 4.0f * q3 * (1.0f - 2.0f * q2q2 - 2.0f * q3q3 - az) + (-_4bx * q3 - _2bz * q1) * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (_2bx * q2 + _2bz * q4) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + (_2bx * q1 - _4bz * q3) * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz);
s4 = _2q2 * (2.0f * q2q4 - _2q1q3 - ax) + _2q3 * (2.0f * q1q2 + _2q3q4 - ay) + (-_4bx * q4 + _2bz * q2) * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (-_2bx * q1 + _2bz * q3) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + _2bx * q2 * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz);
norm = sqrt(s1 * s1 + s2 * s2 + s3 * s3 + s4 * s4); // normalise step magnitude
norm = 1.0f/norm;
s1 *= norm;
s2 *= norm;
s3 *= norm;
s4 *= norm;
// Compute rate of change of quaternion
qDot1 = 0.5f * (-q2 * gx - q3 * gy - q4 * gz) - beta * s1;
qDot2 = 0.5f * (q1 * gx + q3 * gz - q4 * gy) - beta * s2;
qDot3 = 0.5f * (q1 * gy - q2 * gz + q4 * gx) - beta * s3;
qDot4 = 0.5f * (q1 * gz + q2 * gy - q3 * gx) - beta * s4;
// Integrate to yield quaternion
q1 += qDot1 * deltat;
q2 += qDot2 * deltat;
q3 += qDot3 * deltat;
q4 += qDot4 * deltat;
norm = sqrt(q1 * q1 + q2 * q2 + q3 * q3 + q4 * q4); // normalise quaternion
norm = 1.0f/norm;
q[0] = q1 * norm;
q[1] = q2 * norm;
q[2] = q3 * norm;
q[3] = q4 * norm;
}
// Similar to Madgwick scheme but uses proportional and integral filtering on
// the error between estimated reference vectors and measured ones.
void MahonyQuaternionUpdate(float ax, float ay, float az, float gx, float gy, float gz, float mx, float my, float mz, float deltat)
{
// short name local variable for readability
float q1 = q[0], q2 = q[1], q3 = q[2], q4 = q[3];
float norm;
float hx, hy, bx, bz;
float vx, vy, vz, wx, wy, wz;
float ex, ey, ez;
float pa, pb, pc;
// Auxiliary variables to avoid repeated arithmetic
float q1q1 = q1 * q1;
float q1q2 = q1 * q2;
float q1q3 = q1 * q3;
float q1q4 = q1 * q4;
float q2q2 = q2 * q2;
float q2q3 = q2 * q3;
float q2q4 = q2 * q4;
float q3q3 = q3 * q3;
float q3q4 = q3 * q4;
float q4q4 = q4 * q4;
// Normalise accelerometer measurement
norm = sqrt(ax * ax + ay * ay + az * az);
if (norm == 0.0f) return; // Handle NaN
norm = 1.0f / norm; // Use reciprocal for division
ax *= norm;
ay *= norm;
az *= norm;
// Normalise magnetometer measurement
norm = sqrt(mx * mx + my * my + mz * mz);
if (norm == 0.0f) return; // Handle NaN
norm = 1.0f / norm; // Use reciprocal for division
mx *= norm;
my *= norm;
mz *= norm;
// Reference direction of Earth's magnetic field
hx = 2.0f * mx * (0.5f - q3q3 - q4q4) + 2.0f * my * (q2q3 - q1q4) + 2.0f * mz * (q2q4 + q1q3);
hy = 2.0f * mx * (q2q3 + q1q4) + 2.0f * my * (0.5f - q2q2 - q4q4) + 2.0f * mz * (q3q4 - q1q2);
bx = sqrt((hx * hx) + (hy * hy));
bz = 2.0f * mx * (q2q4 - q1q3) + 2.0f * my * (q3q4 + q1q2) + 2.0f * mz * (0.5f - q2q2 - q3q3);
// Estimated direction of gravity and magnetic field
vx = 2.0f * (q2q4 - q1q3);
vy = 2.0f * (q1q2 + q3q4);
vz = q1q1 - q2q2 - q3q3 + q4q4;
wx = 2.0f * bx * (0.5f - q3q3 - q4q4) + 2.0f * bz * (q2q4 - q1q3);
wy = 2.0f * bx * (q2q3 - q1q4) + 2.0f * bz * (q1q2 + q3q4);
wz = 2.0f * bx * (q1q3 + q2q4) + 2.0f * bz * (0.5f - q2q2 - q3q3);
// Error is cross product between estimated direction and measured direction of gravity
ex = (ay * vz - az * vy) + (my * wz - mz * wy);
ey = (az * vx - ax * vz) + (mz * wx - mx * wz);
ez = (ax * vy - ay * vx) + (mx * wy - my * wx);
if (Ki > 0.0f)
{
eInt[0] += ex; // accumulate integral error
eInt[1] += ey;
eInt[2] += ez;
}
else
{
eInt[0] = 0.0f; // prevent integral wind up
eInt[1] = 0.0f;
eInt[2] = 0.0f;
}
// Apply feedback terms
gx = gx + Kp * ex + Ki * eInt[0];
gy = gy + Kp * ey + Ki * eInt[1];
gz = gz + Kp * ez + Ki * eInt[2];
// Integrate rate of change of quaternion
pa = q2;
pb = q3;
pc = q4;
q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * deltat);
q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * deltat);
q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * deltat);
q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * deltat);
// Normalise quaternion
norm = sqrt(q1 * q1 + q2 * q2 + q3 * q3 + q4 * q4);
norm = 1.0f / norm;
q[0] = q1 * norm;
q[1] = q2 * norm;
q[2] = q3 * norm;
q[3] = q4 * norm;
}
const float * getQ () { return q; }
aaaand .h
#ifndef _QUATERNIONFILTERS_H_
#define _QUATERNIONFILTERS_H_
#include <Arduino.h>
void MadgwickQuaternionUpdate(float ax, float ay, float az, float gx, float gy,
float gz, float mx, float my, float mz,
float deltat);
void MahonyQuaternionUpdate(float ax, float ay, float az, float gx, float gy,
float gz, float mx, float my, float mz,
float deltat);
const float * getQ();
#endif // _QUATERNIONFILTERS_H_
That is very difficult.
Calculations with floats are very slowly on 8 bit processors. Try to use int where possible or choose for a 32 bit processor.

Quaternion - Conversion between YawPitchRoll and EulerAngles produces incorrect result only with pitch of Pi

I have spent some time implementing a couple of algorithms for converting between EulerAngles and Quaternions.
I am testing that the quaternion values are the same with this code
Quaternion orientation0 = Prototype1.Mathematics.ToolBox.QuaternionFromYawPitchRoll(0, 0, 0);
Vector3 rotation = orientation0.ToEulerAngles();
Quaternion orientation1 = Prototype1.Mathematics.ToolBox.QuaternionFromYawPitchRoll(rotation.Y, rotation.X, rotation.Z);
Console.WriteLine(orientation0);
Console.WriteLine(orientation1);
I have used a previous method discussed here and have since implemented another method described here
public static Quaternion QuaternionFromYawPitchRoll(float yaw, float pitch, float roll)
{
float rollOver2 = roll * 0.5f;
float sinRollOver2 = (float)Math.Sin((double)rollOver2);
float cosRollOver2 = (float)Math.Cos((double)rollOver2);
float pitchOver2 = pitch * 0.5f;
float sinPitchOver2 = (float)Math.Sin((double)pitchOver2);
float cosPitchOver2 = (float)Math.Cos((double)pitchOver2);
float yawOver2 = yaw * 0.5f;
float sinYawOver2 = (float)Math.Sin((double)yawOver2);
float cosYawOver2 = (float)Math.Cos((double)yawOver2);
// X = PI is giving incorrect result (pitch)
// Heading = Yaw
// Attitude = Pitch
// Bank = Roll
Quaternion result;
//result.X = cosYawOver2 * cosPitchOver2 * cosRollOver2 + sinYawOver2 * sinPitchOver2 * sinRollOver2;
//result.Y = cosYawOver2 * cosPitchOver2 * sinRollOver2 - sinYawOver2 * sinPitchOver2 * cosRollOver2;
//result.Z = cosYawOver2 * sinPitchOver2 * cosRollOver2 + sinYawOver2 * cosPitchOver2 * sinRollOver2;
//result.W = sinYawOver2 * cosPitchOver2 * cosRollOver2 - cosYawOver2 * sinPitchOver2 * sinRollOver2;
result.W = cosYawOver2 * cosPitchOver2 * cosRollOver2 - sinYawOver2 * sinPitchOver2 * sinRollOver2;
result.X = sinYawOver2 * sinPitchOver2 * cosRollOver2 + cosYawOver2 * cosPitchOver2 * sinRollOver2;
result.Y = sinYawOver2 * cosPitchOver2 * cosRollOver2 + cosYawOver2 * sinPitchOver2 * sinRollOver2;
result.Z = cosYawOver2 * sinPitchOver2 * cosRollOver2 - sinYawOver2 * cosPitchOver2 * sinRollOver2;
return result;
}
public static Vector3 ToEulerAngles(this Quaternion q)
{
// Store the Euler angles in radians
Vector3 pitchYawRoll = new Vector3();
double sqx = q.X * q.X;
double sqy = q.Y * q.Y;
double sqz = q.Z * q.Z;
double sqw = q.W * q.W;
// If quaternion is normalised the unit is one, otherwise it is the correction factor
double unit = sqx + sqy + sqz + sqw;
double test = q.X * q.Y + q.Z * q.W;
//double test = q.X * q.Z - q.W * q.Y;
if (test > 0.4999f * unit) // 0.4999f OR 0.5f - EPSILON
{
// Singularity at north pole
pitchYawRoll.Y = 2f * (float)Math.Atan2(q.X, q.W); // Yaw
pitchYawRoll.X = PIOVER2; // Pitch
pitchYawRoll.Z = 0f; // Roll
return pitchYawRoll;
}
else if (test < -0.4999f * unit) // -0.4999f OR -0.5f + EPSILON
{
// Singularity at south pole
pitchYawRoll.Y = -2f * (float)Math.Atan2(q.X, q.W); // Yaw
pitchYawRoll.X = -PIOVER2; // Pitch
pitchYawRoll.Z = 0f; // Roll
return pitchYawRoll;
}
else
{
pitchYawRoll.Y = (float)Math.Atan2(2f * q.Y * q.W - 2f * q.X * q.Z, sqx - sqy - sqz + sqw); // Yaw
pitchYawRoll.X = (float)Math.Asin(2f * test / unit); // Pitch
pitchYawRoll.Z = (float)Math.Atan2(2f * q.X * q.W - 2f * q.Y * q.Z, -sqx + sqy - sqz + sqw); // Roll
//pitchYawRoll.Y = (float)Math.Atan2(2f * q.X * q.W + 2f * q.Y * q.Z, 1 - 2f * (sqz + sqw)); // Yaw
//pitchYawRoll.X = (float)Math.Asin(2f * (q.X * q.Z - q.W * q.Y)); // Pitch
//pitchYawRoll.Z = (float)Math.Atan2(2f * q.X * q.Y + 2f * q.Z * q.W, 1 - 2f * (sqy + sqz)); // Roll
}
return pitchYawRoll;
}
All my implementations work except for when the pitch value is ±PI.
Quaternion orientation0 = Prototype1.Mathematics.ToolBox.QuaternionFromYawPitchRoll(0, PI, 0);
Vector3 rotation = orientation0.ToEulerAngles();
Quaternion orientation1 = Prototype1.Mathematics.ToolBox.QuaternionFromYawPitchRoll(rotation.Y, rotation.X, rotation.Z);
Console.WriteLine(orientation0);
Console.WriteLine(orientation1); // Not the same quaternion values
Why will this not work for that particular value? If it is a singularity then it is not being determined as one in the algorithm and the 'test' value will instead be very close to 0.
Rotation space wraps onto itself. Obviously if you rotate by 2PI around any axis, you end up back where you started. Likewise, if you rotate by PI around an axis, it's the same thing as rotating by -PI around the same axis. Or if you rotate by any angle around an axis, it's the same as rotating by the negation of that angle around the negation of that axis.
All of this means that your quaternion conversion algorithms have to decide what to do when dealing with redundancy. The two orientations that you provide in the comments are the same orientation: (0,0,0,1) and (0,0,0,-1) [I prefer having 'w' in alphabetical order].
You should make sure that you always normalize your quaternions or else you'll eventually get some strange drifting. Other than that, what seems to be happening is that when you rotate by PI around the 'z' axis, floating point round-off or a 'less-than' vs. 'less-than-or-equal-to' discrepancy is pushing the representation around the circle to the point that your algorithm decides to represent the angle as a rotation by -PI around the z-axis. That's the same thing.
In a similar manner, if you rotate by 2PI around any axis, your quaternion might be (-1,0,0,0). But if you rotate by zero, it will be (1,0,0,0). The Euler angle representation coming back from either of those quaternions, however, should be (0,0,0).

Intersection between a line and a sphere

I'm trying to find the point of intersection between a sphere and a line but honestly, I don't have any idea of how to do so.
Could anyone help me on this one ?
Express the line as an function of t:
{ x(t) = x0*(1-t) + t*x1
{ y(t) = y0*(1-t) + t*y1
{ z(t) = z0*(1-t) + t*z1
When t = 0, it will be at one end-point (x0,y0,z0). When t = 1, it will be at the other end-point (x1,y1,z1).
Write a formula for the distance to the center of the sphere (squared) in t (where (xc,yc,zc) is the center of the sphere):
f(t) = (x(t) - xc)^2 + (y(t) - yc)^2 + (z(t) - zc)^2
Solve for t when f(t) equals R^2 (R being the radius of the sphere):
(x(t) - xc)^2 + (y(t) - yc)^2 + (z(t) - zc)^2 = R^2
A = (x0-xc)^2 + (y0-yc)^2 + (z0-zc)^2 - R^2
B = (x1-xc)^2 + (y1-yc)^2 + (z1-zc)^2 - A - C - R^2
C = (x0-x1)^2 + (y0-y1)^2 + (z0-z1)^2
Solve A + B*t + C*t^2 = 0 for t. This is a normal quadratic equation.
You can get up to two solutions. Any solution where t lies between 0 and 1 are valid.
If you got a valid solution for t, plug it in the first equations to get the point of intersection.
I assumed you meant a line segment (two end-points). If you instead want a full line (infinite length), then you could pick two points along the line (not too close), and use them. Also let t be any real value, not just between 0 and 1.
Edit: I fixed the formula for B. I was mixing up the signs. Thanks M Katz, for mentioning that it didn't work.
I believe there is an inaccuracy in the solution by Markus Jarderot. Not sure what the problem is, but I'm pretty sure I translated it faithfully to code, and when I tried to find the intersection of a line segment known to cross into a sphere, I got a negative discriminant (no solutions).
I found this: http://www.codeproject.com/Articles/19799/Simple-Ray-Tracing-in-C-Part-II-Triangles-Intersec, which gives a similar but slightly different derivation.
I turned that into the following C# code and it works for me:
public static Point3D[] FindLineSphereIntersections( Point3D linePoint0, Point3D linePoint1, Point3D circleCenter, double circleRadius )
{
// http://www.codeproject.com/Articles/19799/Simple-Ray-Tracing-in-C-Part-II-Triangles-Intersec
double cx = circleCenter.X;
double cy = circleCenter.Y;
double cz = circleCenter.Z;
double px = linePoint0.X;
double py = linePoint0.Y;
double pz = linePoint0.Z;
double vx = linePoint1.X - px;
double vy = linePoint1.Y - py;
double vz = linePoint1.Z - pz;
double A = vx * vx + vy * vy + vz * vz;
double B = 2.0 * (px * vx + py * vy + pz * vz - vx * cx - vy * cy - vz * cz);
double C = px * px - 2 * px * cx + cx * cx + py * py - 2 * py * cy + cy * cy +
pz * pz - 2 * pz * cz + cz * cz - circleRadius * circleRadius;
// discriminant
double D = B * B - 4 * A * C;
if ( D < 0 )
{
return new Point3D[ 0 ];
}
double t1 = ( -B - Math.Sqrt ( D ) ) / ( 2.0 * A );
Point3D solution1 = new Point3D( linePoint0.X * ( 1 - t1 ) + t1 * linePoint1.X,
linePoint0.Y * ( 1 - t1 ) + t1 * linePoint1.Y,
linePoint0.Z * ( 1 - t1 ) + t1 * linePoint1.Z );
if ( D == 0 )
{
return new Point3D[] { solution1 };
}
double t2 = ( -B + Math.Sqrt( D ) ) / ( 2.0 * A );
Point3D solution2 = new Point3D( linePoint0.X * ( 1 - t2 ) + t2 * linePoint1.X,
linePoint0.Y * ( 1 - t2 ) + t2 * linePoint1.Y,
linePoint0.Z * ( 1 - t2 ) + t2 * linePoint1.Z );
// prefer a solution that's on the line segment itself
if ( Math.Abs( t1 - 0.5 ) < Math.Abs( t2 - 0.5 ) )
{
return new Point3D[] { solution1, solution2 };
}
return new Point3D[] { solution2, solution1 };
}
Don't have enough reputation to comment on M Katz answer, but his answer assumes that the line can go on infinitely in each direction. If you need only the line SEGMENT's intersection points, you need t1 and t2 to be less than one (based on the definition of a parameterized equation). Please see my answer in C# below:
public static Point3D[] FindLineSphereIntersections(Point3D linePoint0, Point3D linePoint1, Point3D circleCenter, double circleRadius)
{
double cx = circleCenter.X;
double cy = circleCenter.Y;
double cz = circleCenter.Z;
double px = linePoint0.X;
double py = linePoint0.Y;
double pz = linePoint0.Z;
double vx = linePoint1.X - px;
double vy = linePoint1.Y - py;
double vz = linePoint1.Z - pz;
double A = vx * vx + vy * vy + vz * vz;
double B = 2.0 * (px * vx + py * vy + pz * vz - vx * cx - vy * cy - vz * cz);
double C = px * px - 2 * px * cx + cx * cx + py * py - 2 * py * cy + cy * cy +
pz * pz - 2 * pz * cz + cz * cz - circleRadius * circleRadius;
// discriminant
double D = B * B - 4 * A * C;
double t1 = (-B - Math.Sqrt(D)) / (2.0 * A);
Point3D solution1 = new Point3D(linePoint0.X * (1 - t1) + t1 * linePoint1.X,
linePoint0.Y * (1 - t1) + t1 * linePoint1.Y,
linePoint0.Z * (1 - t1) + t1 * linePoint1.Z);
double t2 = (-B + Math.Sqrt(D)) / (2.0 * A);
Point3D solution2 = new Point3D(linePoint0.X * (1 - t2) + t2 * linePoint1.X,
linePoint0.Y * (1 - t2) + t2 * linePoint1.Y,
linePoint0.Z * (1 - t2) + t2 * linePoint1.Z);
if (D < 0 || t1 > 1 || t2 >1)
{
return new Point3D[0];
}
else if (D == 0)
{
return new [] { solution1 };
}
else
{
return new [] { solution1, solution2 };
}
}
You may use Wolfram Alpha to solve it in the coordinate system where the sphere is centered.
In this system, the equations are:
Sphere:
x^2 + y^2 + z^2 = r^2
Straight line:
x = x0 + Cos[x1] t
y = y0 + Cos[y1] t
z = z0 + Cos[z1] t
Then we ask Wolfram Alpha to solve for t: (Try it!)
and after that you may change again to your original coordinate system (a simple translation)
Find the solution of the two equations in (x,y,z) describing the line and the sphere.
There may be 0, 1 or 2 solutions.
0 implies they don't intersect
1 implies the line is a tangent to the sphere
2 implies the line passes through the sphere.
Here's a more concise formulation using inner products, less than 100 LOCs, and no external links. Also, the question was asked for a line, not a line segment.
Assume that the sphere is centered at C with radius r. The line is described by P+l*D where D*D=1. P and C are points, D is a vector, l is a number.
We set PC = P-C, pd = PC*D and s = pd*pd - PC*PC + r*r. If s < 0 there are no solutions, if s == 0 there is just one, otherwise there are two. For the solutions we set l = -pd +- sqrt(s), then plug into P+l*D.
Or you can just find the formula of both:
line: (x-x0)/a=(y-y0)/b=(z-z0)/c, which are symmetric equations of the line segment between the points you can find.
sphere: (x-xc)^2+(y-yc)^2+(z-zc)^2 = R^2.
Use the symmetric equation to find relationship between x and y, and x and z.
Then plug in y and z in terms of x into the equation of the sphere.
Then find x, and then you can find y and z.
If x gives you an imaginary result, that means the line and the sphere doesn't intersect.
I don't have the reputation to comment on Ashavsky's solution, but the check at the end needed a bit more tweaking.
if (D < 0)
return new Point3D[0];
else if ((t1 > 1 || t1 < 0) && (t2 > 1 || t2 < 0))
return new Point3D[0];
else if (!(t1 > 1 || t1 < 0) && (t2 > 1 || t2 < 0))
return new [] { solution1 };
else if ((t1 > 1 || t1 < 0) && !(t2 > 1 || t2 < 0))
return new [] { solution2 };
else if (D == 0)
return new [] { solution1 };
else
return new [] { solution1, solution2 };

Calculate distance between 2 GPS coordinates

How do I calculate distance between two GPS coordinates (using latitude and longitude)?
Calculate the distance between two coordinates by latitude and longitude, including a Javascript implementation.
West and South locations are negative.
Remember minutes and seconds are out of 60 so S31 30' is -31.50 degrees.
Don't forget to convert degrees to radians. Many languages have this function. Or its a simple calculation: radians = degrees * PI / 180.
function degreesToRadians(degrees) {
return degrees * Math.PI / 180;
}
function distanceInKmBetweenEarthCoordinates(lat1, lon1, lat2, lon2) {
var earthRadiusKm = 6371;
var dLat = degreesToRadians(lat2-lat1);
var dLon = degreesToRadians(lon2-lon1);
lat1 = degreesToRadians(lat1);
lat2 = degreesToRadians(lat2);
var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return earthRadiusKm * c;
}
Here are some examples of usage:
distanceInKmBetweenEarthCoordinates(0,0,0,0) // Distance between same
// points should be 0
0
distanceInKmBetweenEarthCoordinates(51.5, 0, 38.8, -77.1) // From London
// to Arlington
5918.185064088764
Look for haversine with Google; here is my solution:
#include <math.h>
#include "haversine.h"
#define d2r (M_PI / 180.0)
//calculate haversine distance for linear distance
double haversine_km(double lat1, double long1, double lat2, double long2)
{
double dlong = (long2 - long1) * d2r;
double dlat = (lat2 - lat1) * d2r;
double a = pow(sin(dlat/2.0), 2) + cos(lat1*d2r) * cos(lat2*d2r) * pow(sin(dlong/2.0), 2);
double c = 2 * atan2(sqrt(a), sqrt(1-a));
double d = 6367 * c;
return d;
}
double haversine_mi(double lat1, double long1, double lat2, double long2)
{
double dlong = (long2 - long1) * d2r;
double dlat = (lat2 - lat1) * d2r;
double a = pow(sin(dlat/2.0), 2) + cos(lat1*d2r) * cos(lat2*d2r) * pow(sin(dlong/2.0), 2);
double c = 2 * atan2(sqrt(a), sqrt(1-a));
double d = 3956 * c;
return d;
}
C# Version of Haversine
double _eQuatorialEarthRadius = 6378.1370D;
double _d2r = (Math.PI / 180D);
private int HaversineInM(double lat1, double long1, double lat2, double long2)
{
return (int)(1000D * HaversineInKM(lat1, long1, lat2, long2));
}
private double HaversineInKM(double lat1, double long1, double lat2, double long2)
{
double dlong = (long2 - long1) * _d2r;
double dlat = (lat2 - lat1) * _d2r;
double a = Math.Pow(Math.Sin(dlat / 2D), 2D) + Math.Cos(lat1 * _d2r) * Math.Cos(lat2 * _d2r) * Math.Pow(Math.Sin(dlong / 2D), 2D);
double c = 2D * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1D - a));
double d = _eQuatorialEarthRadius * c;
return d;
}
Here's a .NET Fiddle of this, so you can test it out with your own Lat/Longs.
Java Version of Haversine Algorithm based on Roman Makarov`s reply to this thread
public class HaversineAlgorithm {
static final double _eQuatorialEarthRadius = 6378.1370D;
static final double _d2r = (Math.PI / 180D);
public static int HaversineInM(double lat1, double long1, double lat2, double long2) {
return (int) (1000D * HaversineInKM(lat1, long1, lat2, long2));
}
public static double HaversineInKM(double lat1, double long1, double lat2, double long2) {
double dlong = (long2 - long1) * _d2r;
double dlat = (lat2 - lat1) * _d2r;
double a = Math.pow(Math.sin(dlat / 2D), 2D) + Math.cos(lat1 * _d2r) * Math.cos(lat2 * _d2r)
* Math.pow(Math.sin(dlong / 2D), 2D);
double c = 2D * Math.atan2(Math.sqrt(a), Math.sqrt(1D - a));
double d = _eQuatorialEarthRadius * c;
return d;
}
}
This is very easy to do with geography type in SQL Server 2008.
SELECT geography::Point(lat1, lon1, 4326).STDistance(geography::Point(lat2, lon2, 4326))
-- computes distance in meters using eliptical model, accurate to the mm
4326 is SRID for WGS84 elipsoidal Earth model
Here's a Haversine function in Python that I use:
from math import pi,sqrt,sin,cos,atan2
def haversine(pos1, pos2):
lat1 = float(pos1['lat'])
long1 = float(pos1['long'])
lat2 = float(pos2['lat'])
long2 = float(pos2['long'])
degree_to_rad = float(pi / 180.0)
d_lat = (lat2 - lat1) * degree_to_rad
d_long = (long2 - long1) * degree_to_rad
a = pow(sin(d_lat / 2), 2) + cos(lat1 * degree_to_rad) * cos(lat2 * degree_to_rad) * pow(sin(d_long / 2), 2)
c = 2 * atan2(sqrt(a), sqrt(1 - a))
km = 6367 * c
mi = 3956 * c
return {"km":km, "miles":mi}
I needed to calculate a lot of distances between the points for my project, so I went ahead and tried to optimize the code, I have found here. On average in different browsers my new implementation runs 2 times faster than the most upvoted answer.
function distance(lat1, lon1, lat2, lon2) {
var p = 0.017453292519943295; // Math.PI / 180
var c = Math.cos;
var a = 0.5 - c((lat2 - lat1) * p)/2 +
c(lat1 * p) * c(lat2 * p) *
(1 - c((lon2 - lon1) * p))/2;
return 12742 * Math.asin(Math.sqrt(a)); // 2 * R; R = 6371 km
}
You can play with my jsPerf and see the results here.
Recently I needed to do the same in python, so here is a python implementation:
from math import cos, asin, sqrt
def distance(lat1, lon1, lat2, lon2):
p = 0.017453292519943295
a = 0.5 - cos((lat2 - lat1) * p)/2 + cos(lat1 * p) * cos(lat2 * p) * (1 - cos((lon2 - lon1) * p)) / 2
return 12742 * asin(sqrt(a))
And for the sake of completeness: Haversine on wiki.
It depends on how accurate you need it to be. If you need pinpoint accuracy, it is best to look at an algorithm which uses an ellipsoid, rather than a sphere, such as Vincenty's algorithm, which is accurate to the mm.
Here it is in C# (lat and long in radians):
double CalculateGreatCircleDistance(double lat1, double long1, double lat2, double long2, double radius)
{
return radius * Math.Acos(
Math.Sin(lat1) * Math.Sin(lat2)
+ Math.Cos(lat1) * Math.Cos(lat2) * Math.Cos(long2 - long1));
}
If your lat and long are in degrees then divide by 180/PI to convert to radians.
PHP version:
(Remove all deg2rad() if your coordinates are already in radians.)
$R = 6371; // km
$dLat = deg2rad($lat2-$lat1);
$dLon = deg2rad($lon2-$lon1);
$lat1 = deg2rad($lat1);
$lat2 = deg2rad($lat2);
$a = sin($dLat/2) * sin($dLat/2) +
sin($dLon/2) * sin($dLon/2) * cos($lat1) * cos($lat2);
$c = 2 * atan2(sqrt($a), sqrt(1-$a));
$d = $R * $c;
A T-SQL function, that I use to select records by distance for a center
Create Function [dbo].[DistanceInMiles]
( #fromLatitude float ,
#fromLongitude float ,
#toLatitude float,
#toLongitude float
)
returns float
AS
BEGIN
declare #distance float
select #distance = cast((3963 * ACOS(round(COS(RADIANS(90-#fromLatitude))*COS(RADIANS(90-#toLatitude))+
SIN(RADIANS(90-#fromLatitude))*SIN(RADIANS(90-#toLatitude))*COS(RADIANS(#fromLongitude-#toLongitude)),15))
)as float)
return round(#distance,1)
END
I. Regarding "Breadcrumbs" method
Earth radius is different on different Lat. This must be taken into consideration in Haversine algorithm.
Consider Bearing change, which turns straight lines to arches (which are longer)
Taking Speed change into account will turn arches to spirals (which are longer or shorter than arches)
Altitude change will turn flat spirals to 3D spirals (which are longer again). This is very important for hilly areas.
Below see the function in C which takes #1 and #2 into account:
double calcDistanceByHaversine(double rLat1, double rLon1, double rHeading1,
double rLat2, double rLon2, double rHeading2){
double rDLatRad = 0.0;
double rDLonRad = 0.0;
double rLat1Rad = 0.0;
double rLat2Rad = 0.0;
double a = 0.0;
double c = 0.0;
double rResult = 0.0;
double rEarthRadius = 0.0;
double rDHeading = 0.0;
double rDHeadingRad = 0.0;
if ((rLat1 < -90.0) || (rLat1 > 90.0) || (rLat2 < -90.0) || (rLat2 > 90.0)
|| (rLon1 < -180.0) || (rLon1 > 180.0) || (rLon2 < -180.0)
|| (rLon2 > 180.0)) {
return -1;
};
rDLatRad = (rLat2 - rLat1) * DEGREE_TO_RADIANS;
rDLonRad = (rLon2 - rLon1) * DEGREE_TO_RADIANS;
rLat1Rad = rLat1 * DEGREE_TO_RADIANS;
rLat2Rad = rLat2 * DEGREE_TO_RADIANS;
a = sin(rDLatRad / 2) * sin(rDLatRad / 2) + sin(rDLonRad / 2) * sin(
rDLonRad / 2) * cos(rLat1Rad) * cos(rLat2Rad);
if (a == 0.0) {
return 0.0;
}
c = 2 * atan2(sqrt(a), sqrt(1 - a));
rEarthRadius = 6378.1370 - (21.3847 * 90.0 / ((fabs(rLat1) + fabs(rLat2))
/ 2.0));
rResult = rEarthRadius * c;
// Chord to Arc Correction based on Heading changes. Important for routes with many turns and U-turns
if ((rHeading1 >= 0.0) && (rHeading1 < 360.0) && (rHeading2 >= 0.0)
&& (rHeading2 < 360.0)) {
rDHeading = fabs(rHeading1 - rHeading2);
if (rDHeading > 180.0) {
rDHeading -= 180.0;
}
rDHeadingRad = rDHeading * DEGREE_TO_RADIANS;
if (rDHeading > 5.0) {
rResult = rResult * (rDHeadingRad / (2.0 * sin(rDHeadingRad / 2)));
} else {
rResult = rResult / cos(rDHeadingRad);
}
}
return rResult;
}
II. There is an easier way which gives pretty good results.
By Average Speed.
Trip_distance = Trip_average_speed * Trip_time
Since GPS Speed is detected by Doppler effect and is not directly related to [Lon,Lat] it can be at least considered as secondary (backup or correction) if not as main distance calculation method.
If you need something more accurate then have a look at this.
Vincenty's formulae are two related iterative methods used in geodesy
to calculate the distance between two points on the surface of a
spheroid, developed by Thaddeus Vincenty (1975a) They are based on the
assumption that the figure of the Earth is an oblate spheroid, and
hence are more accurate than methods such as great-circle distance
which assume a spherical Earth.
The first (direct) method computes the location of a point which is a
given distance and azimuth (direction) from another point. The second
(inverse) method computes the geographical distance and azimuth
between two given points. They have been widely used in geodesy
because they are accurate to within 0.5 mm (0.020″) on the Earth
ellipsoid.
If you're using .NET don't reivent the wheel. See System.Device.Location. Credit to fnx in the comments in another answer.
using System.Device.Location;
double lat1 = 45.421527862548828D;
double long1 = -75.697189331054688D;
double lat2 = 53.64135D;
double long2 = -113.59273D;
GeoCoordinate geo1 = new GeoCoordinate(lat1, long1);
GeoCoordinate geo2 = new GeoCoordinate(lat2, long2);
double distance = geo1.GetDistanceTo(geo2);
here is the Swift implementation from the answer
func degreesToRadians(degrees: Double) -> Double {
return degrees * Double.pi / 180
}
func distanceInKmBetweenEarthCoordinates(lat1: Double, lon1: Double, lat2: Double, lon2: Double) -> Double {
let earthRadiusKm: Double = 6371
let dLat = degreesToRadians(degrees: lat2 - lat1)
let dLon = degreesToRadians(degrees: lon2 - lon1)
let lat1 = degreesToRadians(degrees: lat1)
let lat2 = degreesToRadians(degrees: lat2)
let a = sin(dLat/2) * sin(dLat/2) +
sin(dLon/2) * sin(dLon/2) * cos(lat1) * cos(lat2)
let c = 2 * atan2(sqrt(a), sqrt(1 - a))
return earthRadiusKm * c
}
This is version from "Henry Vilinskiy" adapted for MySQL and Kilometers:
CREATE FUNCTION `CalculateDistanceInKm`(
fromLatitude float,
fromLongitude float,
toLatitude float,
toLongitude float
) RETURNS float
BEGIN
declare distance float;
select
6367 * ACOS(
round(
COS(RADIANS(90-fromLatitude)) *
COS(RADIANS(90-toLatitude)) +
SIN(RADIANS(90-fromLatitude)) *
SIN(RADIANS(90-toLatitude)) *
COS(RADIANS(fromLongitude-toLongitude))
,15)
)
into distance;
return round(distance,3);
END;
This Lua code is adapted from stuff found on Wikipedia and in Robert Lipe's GPSbabel tool:
local EARTH_RAD = 6378137.0
-- earth's radius in meters (official geoid datum, not 20,000km / pi)
local radmiles = EARTH_RAD*100.0/2.54/12.0/5280.0;
-- earth's radius in miles
local multipliers = {
radians = 1, miles = radmiles, mi = radmiles, feet = radmiles * 5280,
meters = EARTH_RAD, m = EARTH_RAD, km = EARTH_RAD / 1000,
degrees = 360 / (2 * math.pi), min = 60 * 360 / (2 * math.pi)
}
function gcdist(pt1, pt2, units) -- return distance in radians or given units
--- this formula works best for points close together or antipodal
--- rounding error strikes when distance is one-quarter Earth's circumference
--- (ref: wikipedia Great-circle distance)
if not pt1.radians then pt1 = rad(pt1) end
if not pt2.radians then pt2 = rad(pt2) end
local sdlat = sin((pt1.lat - pt2.lat) / 2.0);
local sdlon = sin((pt1.lon - pt2.lon) / 2.0);
local res = sqrt(sdlat * sdlat + cos(pt1.lat) * cos(pt2.lat) * sdlon * sdlon);
res = res > 1 and 1 or res < -1 and -1 or res
res = 2 * asin(res);
if units then return res * assert(multipliers[units])
else return res
end
end
private double deg2rad(double deg)
{
return (deg * Math.PI / 180.0);
}
private double rad2deg(double rad)
{
return (rad / Math.PI * 180.0);
}
private double GetDistance(double lat1, double lon1, double lat2, double lon2)
{
//code for Distance in Kilo Meter
double theta = lon1 - lon2;
double dist = Math.Sin(deg2rad(lat1)) * Math.Sin(deg2rad(lat2)) + Math.Cos(deg2rad(lat1)) * Math.Cos(deg2rad(lat2)) * Math.Cos(deg2rad(theta));
dist = Math.Abs(Math.Round(rad2deg(Math.Acos(dist)) * 60 * 1.1515 * 1.609344 * 1000, 0));
return (dist);
}
private double GetDirection(double lat1, double lon1, double lat2, double lon2)
{
//code for Direction in Degrees
double dlat = deg2rad(lat1) - deg2rad(lat2);
double dlon = deg2rad(lon1) - deg2rad(lon2);
double y = Math.Sin(dlon) * Math.Cos(lat2);
double x = Math.Cos(deg2rad(lat1)) * Math.Sin(deg2rad(lat2)) - Math.Sin(deg2rad(lat1)) * Math.Cos(deg2rad(lat2)) * Math.Cos(dlon);
double direct = Math.Round(rad2deg(Math.Atan2(y, x)), 0);
if (direct < 0)
direct = direct + 360;
return (direct);
}
private double GetSpeed(double lat1, double lon1, double lat2, double lon2, DateTime CurTime, DateTime PrevTime)
{
//code for speed in Kilo Meter/Hour
TimeSpan TimeDifference = CurTime.Subtract(PrevTime);
double TimeDifferenceInSeconds = Math.Round(TimeDifference.TotalSeconds, 0);
double theta = lon1 - lon2;
double dist = Math.Sin(deg2rad(lat1)) * Math.Sin(deg2rad(lat2)) + Math.Cos(deg2rad(lat1)) * Math.Cos(deg2rad(lat2)) * Math.Cos(deg2rad(theta));
dist = rad2deg(Math.Acos(dist)) * 60 * 1.1515 * 1.609344;
double Speed = Math.Abs(Math.Round((dist / Math.Abs(TimeDifferenceInSeconds)) * 60 * 60, 0));
return (Speed);
}
private double GetDuration(DateTime CurTime, DateTime PrevTime)
{
//code for speed in Kilo Meter/Hour
TimeSpan TimeDifference = CurTime.Subtract(PrevTime);
double TimeDifferenceInSeconds = Math.Abs(Math.Round(TimeDifference.TotalSeconds, 0));
return (TimeDifferenceInSeconds);
}
i took the top answer and used it in a Scala program
import java.lang.Math.{atan2, cos, sin, sqrt}
def latLonDistance(lat1: Double, lon1: Double)(lat2: Double, lon2: Double): Double = {
val earthRadiusKm = 6371
val dLat = (lat2 - lat1).toRadians
val dLon = (lon2 - lon1).toRadians
val latRad1 = lat1.toRadians
val latRad2 = lat2.toRadians
val a = sin(dLat / 2) * sin(dLat / 2) + sin(dLon / 2) * sin(dLon / 2) * cos(latRad1) * cos(latRad2)
val c = 2 * atan2(sqrt(a), sqrt(1 - a))
earthRadiusKm * c
}
i curried the function in order to be able to easily produce functions that have one of the two locations fixed and require only a pair of lat/lon to produce distance.
Here's a Kotlin variation:
import kotlin.math.*
class HaversineAlgorithm {
companion object {
private const val MEAN_EARTH_RADIUS = 6371.008
private const val D2R = Math.PI / 180.0
}
private fun haversineInKm(lat1: Double, lon1: Double, lat2: Double, lon2: Double): Double {
val lonDiff = (lon2 - lon1) * D2R
val latDiff = (lat2 - lat1) * D2R
val latSin = sin(latDiff / 2.0)
val lonSin = sin(lonDiff / 2.0)
val a = latSin * latSin + (cos(lat1 * D2R) * cos(lat2 * D2R) * lonSin * lonSin)
val c = 2.0 * atan2(sqrt(a), sqrt(1.0 - a))
return MEAN_EARTH_RADIUS * c
}
}
you can find a implementation of this (with some good explanation) in F# on fssnip
here are the important parts:
let GreatCircleDistance&lt[&ltMeasure&gt] 'u&gt (R : float&lt'u&gt) (p1 : Location) (p2 : Location) =
let degToRad (x : float&ltdeg&gt) = System.Math.PI * x / 180.0&ltdeg/rad&gt
let sq x = x * x
// take the sin of the half and square the result
let sinSqHf (a : float&ltrad&gt) = (System.Math.Sin &gt&gt sq) (a / 2.0&ltrad&gt)
let cos (a : float&ltdeg&gt) = System.Math.Cos (degToRad a / 1.0&ltrad&gt)
let dLat = (p2.Latitude - p1.Latitude) |&gt degToRad
let dLon = (p2.Longitude - p1.Longitude) |&gt degToRad
let a = sinSqHf dLat + cos p1.Latitude * cos p2.Latitude * sinSqHf dLon
let c = 2.0 * System.Math.Atan2(System.Math.Sqrt(a), System.Math.Sqrt(1.0-a))
R * c
I needed to implement this in PowerShell, hope it can help someone else.
Some notes about this method
Don't split any of the lines or the calculation will be wrong
To calculate in KM remove the * 1000 in the calculation of $distance
Change $earthsRadius = 3963.19059 and remove * 1000 in the calculation of $distance the to calulate the distance in miles
I'm using Haversine, as other posts have pointed out Vincenty's formulae is much more accurate
Function MetresDistanceBetweenTwoGPSCoordinates($latitude1, $longitude1, $latitude2, $longitude2)
{
$Rad = ([math]::PI / 180);
$earthsRadius = 6378.1370 # Earth's Radius in KM
$dLat = ($latitude2 - $latitude1) * $Rad
$dLon = ($longitude2 - $longitude1) * $Rad
$latitude1 = $latitude1 * $Rad
$latitude2 = $latitude2 * $Rad
$a = [math]::Sin($dLat / 2) * [math]::Sin($dLat / 2) + [math]::Sin($dLon / 2) * [math]::Sin($dLon / 2) * [math]::Cos($latitude1) * [math]::Cos($latitude2)
$c = 2 * [math]::ATan2([math]::Sqrt($a), [math]::Sqrt(1-$a))
$distance = [math]::Round($earthsRadius * $c * 1000, 0) #Multiple by 1000 to get metres
Return $distance
}
Scala version
def deg2rad(deg: Double) = deg * Math.PI / 180.0
def rad2deg(rad: Double) = rad / Math.PI * 180.0
def getDistanceMeters(lat1: Double, lon1: Double, lat2: Double, lon2: Double) = {
val theta = lon1 - lon2
val dist = Math.sin(deg2rad(lat1)) * Math.sin(deg2rad(lat2)) + Math.cos(deg2rad(lat1)) *
Math.cos(deg2rad(lat2)) * Math.cos(deg2rad(theta))
Math.abs(
Math.round(
rad2deg(Math.acos(dist)) * 60 * 1.1515 * 1.609344 * 1000)
)
}
Here's my implementation in Elixir
defmodule Geo do
#earth_radius_km 6371
#earth_radius_sm 3958.748
#earth_radius_nm 3440.065
#feet_per_sm 5280
#d2r :math.pi / 180
def deg_to_rad(deg), do: deg * #d2r
def great_circle_distance(p1, p2, :km), do: haversine(p1, p2) * #earth_radius_km
def great_circle_distance(p1, p2, :sm), do: haversine(p1, p2) * #earth_radius_sm
def great_circle_distance(p1, p2, :nm), do: haversine(p1, p2) * #earth_radius_nm
def great_circle_distance(p1, p2, :m), do: great_circle_distance(p1, p2, :km) * 1000
def great_circle_distance(p1, p2, :ft), do: great_circle_distance(p1, p2, :sm) * #feet_per_sm
#doc """
Calculate the [Haversine](https://en.wikipedia.org/wiki/Haversine_formula)
distance between two coordinates. Result is in radians. This result can be
multiplied by the sphere's radius in any unit to get the distance in that unit.
For example, multiple the result of this function by the Earth's radius in
kilometres and you get the distance between the two given points in kilometres.
"""
def haversine({lat1, lon1}, {lat2, lon2}) do
dlat = deg_to_rad(lat2 - lat1)
dlon = deg_to_rad(lon2 - lon1)
radlat1 = deg_to_rad(lat1)
radlat2 = deg_to_rad(lat2)
a = :math.pow(:math.sin(dlat / 2), 2) +
:math.pow(:math.sin(dlon / 2), 2) *
:math.cos(radlat1) * :math.cos(radlat2)
2 * :math.atan2(:math.sqrt(a), :math.sqrt(1 - a))
end
end
Dart Version
Haversine Algorithm.
import 'dart:math';
class GeoUtils {
static double _degreesToRadians(degrees) {
return degrees * pi / 180;
}
static double distanceInKmBetweenEarthCoordinates(lat1, lon1, lat2, lon2) {
var earthRadiusKm = 6371;
var dLat = _degreesToRadians(lat2-lat1);
var dLon = _degreesToRadians(lon2-lon1);
lat1 = _degreesToRadians(lat1);
lat2 = _degreesToRadians(lat2);
var a = sin(dLat/2) * sin(dLat/2) +
sin(dLon/2) * sin(dLon/2) * cos(lat1) * cos(lat2);
var c = 2 * atan2(sqrt(a), sqrt(1-a));
return earthRadiusKm * c;
}
}
In Python, you can use the geopy library to compute the geodesic distance using the WGS84 ellipsoid:
from geopy.distance import geodesic
newport_ri = (41.49008, -71.312796)
cleveland_oh = (41.499498, -81.695391)
print(geodesic(newport_ri, cleveland_oh).km)
TypeScript Version
export const degreeToRadian = (degree: number) => {
return degree * Math.PI / 180;
}
export const distanceBetweenEarthCoordinatesInKm = (lat1: number, lon1: number, lat2: number, lon2: number) => {
const earthRadiusInKm = 6371;
const dLat = degreeToRadian(lat2 - lat1);
const dLon = degreeToRadian(lon2 - lon1);
lat1 = degreeToRadian(lat1);
lat2 = degreeToRadian(lat2);
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(lat1) * Math.cos(lat2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return earthRadiusInKm * c;
}
I think a version of the algorithm in R is still missing:
gpsdistance<-function(lat1,lon1,lat2,lon2){
# internal function to change deg to rad
degreesToRadians<- function (degrees) {
return (degrees * pi / 180)
}
R<-6371e3 #radius of Earth in meters
phi1<-degreesToRadians(lat1) # latitude 1
phi2<-degreesToRadians(lat2) # latitude 2
lambda1<-degreesToRadians(lon1) # longitude 1
lambda2<-degreesToRadians(lon2) # longitude 2
delta_phi<-phi1-phi2 # latitude-distance
delta_lambda<-lambda1-lambda2 # longitude-distance
a<-sin(delta_phi/2)*sin(delta_phi/2)+
cos(phi1)*cos(phi2)*sin(delta_lambda/2)*
sin(delta_lambda/2)
cc<-2*atan2(sqrt(a),sqrt(1-a))
distance<- R * cc
return(distance) # in meters
}
For java
public static double degreesToRadians(double degrees) {
return degrees * Math.PI / 180;
}
public static double distanceInKmBetweenEarthCoordinates(Location location1, Location location2) {
double earthRadiusKm = 6371;
double dLat = degreesToRadians(location2.getLatitude()-location1.getLatitude());
double dLon = degreesToRadians(location2.getLongitude()-location1.getLongitude());
double lat1 = degreesToRadians(location1.getLatitude());
double lat2 = degreesToRadians(location2.getLatitude());
double a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return earthRadiusKm * c;
}
For anyone searching for a Delphi/Pascal version:
function GreatCircleDistance(const Lat1, Long1, Lat2, Long2: Double): Double;
var
Lat1Rad, Long1Rad, Lat2Rad, Long2Rad: Double;
const
EARTH_RADIUS_KM = 6378;
begin
Lat1Rad := DegToRad(Lat1);
Long1Rad := DegToRad(Long1);
Lat2Rad := DegToRad(Lat2);
Long2Rad := DegToRad(Long2);
Result := EARTH_RADIUS_KM * ArcCos(Cos(Lat1Rad) * Cos(Lat2Rad) * Cos(Long1Rad - Long2Rad) + Sin(Lat1Rad) * Sin(Lat2Rad));
end;
I take no credit for this code, I originally found it posted by Gary William on a public forum.

Resources