zero-degree
Version:
A module that constantly monitors and returns the direction angle of the target location as the phone rotates or moves. This is a React Native/Expo package.
172 lines (147 loc) • 5.52 kB
JavaScript
import * as Location from 'expo-location';
/// Prototype based design is used insteam of class; because it's powerful
function ZeroDegree(targetLatLon) {
const _TARGET_LAT_LON = targetLatLon;
/* -----------private variables------------- */
let _angleOfUserAndTarget;
let _angleOfPhoneAndTarget
let _lastDegree = 0;
/* -----------Handlers/subscribers----------- */
let _onDegreeUpdate;
let _onError;
let __getLogData;
let _unwatchHeading;
let _unwatchPosition;
/* ------------public properties------------ */
// Setter used to store the callback in a private variable.
// Because setting in a public property will arise a need of
// storing the self reference 'this' to a local variable (like self, that etc)
// to access it from other scopes, like from a private function.
Object.defineProperty(this, "_getLogData", {
set: function (callback) {
__getLogData = callback;
}
});
/* -----------private methods------------- */
function calculateAngleOfPhoneAndTarget(phoneAngle, angleWithUserAndTarget, tag) {
// Convert the degree from north based degree to east based degree and
// clockwise to anti clockwise.
let angle = phoneAngle <= 90 ? 90 - phoneAngle : -(phoneAngle - 450);
// Phone angle wrt target
angle = angleWithUserAndTarget - angle;
// Make the degree equatlly divided to 0 to 180 and -1 to -180 wrt target
if (angle > 180) angle = angle - 360;
else if (angle < -180) angle = angle + 360;
return angle;
}
function calculateAngleWithUserAndTarget(myCoordinate, targetCoordinate) {
let angle = Math.atan2(targetCoordinate.latitude - myCoordinate.latitude, targetCoordinate.longitude - myCoordinate.longitude) * (180 / Math.PI);
if (angle < 0)
angle = 360 + angle;
return angle;
}
async function initGps() {
let permission;
try {
permission = await Location.requestPermissionsAsync();
if (!permission.granted) {
if (_onError) {
_onError({
denied: true,
message: 'Permission to access location was denied'
});
}
return;
}
}
catch (error) {
if (_onError) {
_onError({
permissionError: true,
message: 'Error occurred while getting GPS permission.',
error
});
}
}
try {
let location = await Location.getCurrentPositionAsync({})
_angleOfUserAndTarget = calculateAngleWithUserAndTarget(location.coords, _TARGET_LAT_LON, 'getCurrentPositionAsync');
if (__getLogData)
passLogData('Got initial location data.');
}
catch (error) {
if (_onError) {
_onError({
gpsError: true,
message: 'Error occurred while getting position from GPS.',
error
});
}
}
// Watch location
try {
_unwatchPosition = await Location.watchPositionAsync({
accuracy: Location.Accuracy.Balanced,
timeInterval: 10 * 1000,
distanceInterval: 5 /* meters */
},
location => {
_angleOfUserAndTarget = calculateAngleWithUserAndTarget(location.coords, _TARGET_LAT_LON);
_angleOfPhoneAndTarget = calculateAngleOfPhoneAndTarget(_lastDegree, _angleOfUserAndTarget);
_onDegreeUpdate(_angleOfPhoneAndTarget);
if (__getLogData)
passLogData('Location updated.');
}
);
}
catch (error) {
_onError({
watchPositionError: true,
message: 'Error occureed while watching GPS position.',
error
});
}
}
async function initWatchHeading() {
try {
_unwatchHeading = await Location.watchHeadingAsync(obj => {
_lastDegree = obj.magHeading;
_angleOfPhoneAndTarget = calculateAngleOfPhoneAndTarget(obj.magHeading, _angleOfUserAndTarget);
_onDegreeUpdate(_angleOfPhoneAndTarget);
});
}
catch (error) {
_onError({
watchHeadingError: true,
message: 'GPS watch heading error.',
error
});
}
}
function passLogData(msg) {
__getLogData({
time: new Date().toLocaleTimeString(),
message: msg,
angleOfUserAndTarget: (_angleOfUserAndTarget || 0).toFixed(1),
angleOfPhoneAndTarget: (_angleOfPhoneAndTarget || 0).toFixed(1),
deviceHeadingWrtNorth: (_lastDegree || 0).toFixed(1),
});
}
/* -----------public methods------------- */
this.watchAsync = async function (onDegreeUpdate, onError) {
_onDegreeUpdate = onDegreeUpdate;
_onError = onError;
if (!_onDegreeUpdate) {
throw Error('onDegreeUpdate handler is missing.');
}
if (__getLogData)
passLogData('Waiting for GPS data.');
await initGps();
await initWatchHeading();
};
this.unwatch = function () {
if (_unwatchPosition) _unwatchPosition.remove();
if (_unwatchHeading) _unwatchHeading.remove();
};
}
export default ZeroDegree;