package com.maciekjanusz.compassproject.util;
import android.view.Surface;
import static java.lang.Math.abs;
import static java.lang.Math.atan2;
import static java.lang.Math.cos;
import static java.lang.Math.floor;
import static java.lang.Math.pow;
import static java.lang.Math.sin;
import static java.lang.Math.sqrt;
import static java.lang.Math.toDegrees;
import static java.lang.Math.toRadians;
/**
* This utility contains several mathematical functions for performing processing of values such as
* bearing, roll, pitch, coordinates, speed, distance etc.
*/
public enum CompassMath {;
/**
* The absolute value of maximum valid latitude, in degrees
*/
public static final float MAX_LATITUDE = 90f;
/**
* The absolute value of maximum valid longitude, in degrees
*/
public static final float MAX_LONGITUDE = 180f;
/**
* Maximum value of coordinate minutes or seconds
*/
private static final float MAX_MIN_SEC = 60;
/**
* Earth's radius, in meters
*/
private static final long EARTH_RADIUS = 6371000;
/**
* This function calculates the initial bearing (forward azimuth) from given location
* to destination.
*
* @param fromLat latitude of starting location, in degrees
* @param fromLon longitude of starting location, in degrees
* @param toLat latitude of destination, in degrees
* @param toLon longitude of destination, in degrees
* @return initial bearing from given location to destination, in degrees
*/
public static double calculateBearing(double fromLat, double fromLon,
double toLat, double toLon) {
// convert degree angles to radians
fromLat = toRadians(fromLat);
fromLon = toRadians(fromLon);
toLat = toRadians(toLat);
toLon = toRadians(toLon);
// calculate bearing
double y = sin(toLon - fromLon) * cos(toLat);
double x = cos(fromLat) * sin(toLat)
- sin(fromLat) * cos(toLat) * cos(toLon - fromLon);
// back to degrees
double bearing = toDegrees(atan2(y, x));
// ensure no "overflow" angle
return (bearing + 360f) % 360f;
}
public static double calculateDistance(double fromLat, double fromLon,
double toLat, double toLon) {
// convert degree angles to radians
double deltaLat = toRadians(toLat - fromLat);
double deltaLon = toRadians(toLon - fromLon);
fromLat = toRadians(fromLat);
toLat = toRadians(toLat);
// "haversine"
double a = pow(sin(deltaLat/2.0), 2)
+ cos(fromLat) * cos(toLat)
* pow(sin(deltaLon/2.0), 2);
double c = 2 * atan2(sqrt(a), sqrt(1.0-a));
return EARTH_RADIUS * c;
}
/**
* Function to calculate (estimate) reach time given current speed and
* distance to destination
* @param speed speed in m/s
* @param distance distance in meters
* @return estimated reach time delta, in seconds
*/
public static long calculateTimeToReach(double speed, double distance) {
double time = distance / speed;
return (long) time;
}
/**
* This function is used to adjust raw bearing according to screen rotation and
* device roll. If the device is upside down, bearing is inverted. Then, current rotation
* of the device is added to the bearing.
*
* @param bearing raw bearing in degrees, as returned from compass instance
* @param roll roll in degrees (-180 : 180), as returned from compass instance
* @param rotation {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90},
* {@link Surface#ROTATION_180}, {@link Surface#ROTATION_270}
* @return adjusted bearing in degrees
*/
public static float adjustBearing(float bearing, float roll, int rotation) {
// if device is more or less upside down, inverse the bearing
if(roll > 90 || roll <= -90) bearing = -bearing;
// ensure that bearing is properly adjusted according to current screen rotation
switch(rotation) {
case Surface.ROTATION_90:
bearing = (bearing + 90) % 360;
break;
case Surface.ROTATION_180:
bearing = (bearing + 180) % 360;
break;
case Surface.ROTATION_270:
bearing = (bearing + 270) % 360;
break;
// default rotation (ROTATION_0 - upright portrait mode) requires no further adjustment.
}
// bearing must be inverted for display
return bearing;
}
/**
* This function checks if the passed latitude value lies between -{@link #MAX_LATITUDE} and
* {@link #MAX_LATITUDE} (-90 : 90)
* @param latitude latitude, in degrees
* @return true if valid, false otherwise
*/
public static boolean validateLatitude(float latitude) {
return latitude >= -MAX_LATITUDE && latitude <= MAX_LATITUDE;
}
/**
* This function checks if the passed longitude value lies between -{@link #MAX_LONGITUDE} and
* {@link #MAX_LONGITUDE} (-180 : 180)
* @param longitude longitude, in degrees
* @return true if valid, false otherwise
*/
public static boolean validateLongitude(float longitude) {
return longitude >= -MAX_LONGITUDE && longitude <= MAX_LONGITUDE;
}
/**
* This function checks if the passeed value qualify as 0 <= x < {@link #MAX_MIN_SEC}
* @param minSec value to check
* @return true if valid
*/
public static boolean validateMinSec(float minSec) {
return minSec >= 0 && minSec < MAX_MIN_SEC;
}
/**
* Converts value in meters/second to kilometers/hour.
* @param speedMs value [m/s]
* @return value [km/h]
*/
public static float msToKmH(float speedMs) {
return speedMs * 3.6f;
}
/**
* Converts value in meters/second to miles per hour.
* @param speedMs value [m/s]
* @return value [mph]
*/
public static float msToMpH(float speedMs) {
return speedMs * 2.23694f;
}
/**
* Converts value in meters to miles
* @param meters value [m]
* @return value [mi]
*/
public static float metersToMiles(float meters) {
return meters * 0.00062137f;
}
/**
* Converts coordinates in DMS (degree, minute, second) format to decimal format.
* @param degrees degrees of coordinate
* @param minutes minutes of coordinate
* @param seconds seconds of coordinate
* @return coordinate in decimal format
*/
public static float degreesToDecimal(int degrees, int minutes, float seconds) {
return degrees + (minutes / 60f) + (seconds / 3600f);
}
/**
* Converts coordinates in decimal format to DMS (degree, minute, second) format.
* @param decimal coordinate in decimal format
* @return coordinate in DMS format
*/
public static float[] decimalToDegrees(double decimal) {
int deg = (int) (floor(decimal * 1000000f) / 1000000f);
int min = (int) ((floor(abs(decimal) * 1000000f * 60f) / 1000000f) % 60f);
float sec = (float) ((abs(decimal) * 3600f) % 60f);
return new float[] {deg, min, sec};
}
}