Creating animated direction indicators in the "aroundMe" style with ngCordova

I am attempting to recreate a compass or arrow similar to the one featured in the AroundMe Mobile App. This arrow should accurately point towards a pin on the map based on my mobile device's position and update as I move.

I have been struggling to figure out how to achieve this functionality and have not been able to find any comprehensive guides or tutorials on the topic.

My research led me to a bearing function, which I incorporated into a directive:

app.directive('arrow', function () {

    function bearing(lat1, lng1, lat2, lng2) {
      var dLon = (lng2 - lng1);
      var y = Math.sin(dLon) * Math.cos(lat2);
      var x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(dLon);
      var rad = Math.atan2(y, x);
      var brng = toDeg(rad);
      return (brng + 360) % 360;
    }

    function toRad(deg) {
      return deg * Math.PI / 180;
    }

    function toDeg(rad) {
      return rad * 180 / Math.PI;
    }

    return {
    restrict: 'E',
    link: function (scope, element, attrs) {
      var arrowAngle = bearing(scope.user.position.lat, scope.user.position.lng, attrs.lat, attrs.lng);
      element.parent().css('transform', 'rotate(' + arrowAngle + 'deg)');
    }
  };

});

Although this code updates the arrow direction, it does not take into account the mobile device's magnetic heading information.

To address this, I included the ngCordova plugin for Device Orientation to retrieve the magneticHeading, but I am unsure of how to incorporate it into the existing bearing function.

  $cordovaDeviceOrientation.getCurrentHeading().then(function(result) {
    var magneticHeading = result.magneticHeading;
    var arrowAngle = bearing(scope.user.position.lat, scope.user.position.lng, attrs.lat, attrs.lng, magneticHeading);
    element.parent().css('transform', 'rotate(' + arrowAngle + 'deg)');
  });

I attempted to modify the return statement as follows:

return (brng - heading) % 360;

or:

return (heading - ((brng + 360) % 360));

Despite implementing this code with a watcher, the arrow is not pointing in the correct direction; for instance, it should be facing North but instead points East.

After extensive online searches, I have not found any resources detailing how to calculate the bearing between a lat/lng point and a magneticHeading.

I feel like I may be close to a solution, but I am unable to progress further independently.

In my attempts to resolve the issue, I have also looked into mathematical formulas, but understanding and implementing them has proven challenging.

Your assistance would be greatly appreciated.

Answer №1

Addressing this question straightforwardly is challenging due to the variety of factors that influence the graphical representation involved. For example, when utilizing rotate(0deg), the direction can vary.

I can provide an explanation of the formula you've discovered, which may assist in resolving the issue independently. The complexity lies in the following:

  var dLon = (lng2 - lng1);
  var y = Math.sin(dLon) * Math.cos(lat2);
  var x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(dLon);
  var rad = Math.atan2(y, x);

This formula utilizes Haversine's theorem (https://en.wikipedia.org/wiki/Haversine_formula). With latitude and longitude coordinates, a standard angle calculation won't suffice due to Earth's spherical shape. The first three lines represent Haversine calculations, resulting in coordinates on the unit circle (https://en.wikipedia.org/wiki/Unit_circle)

The subsequent step involves determining the angle from the point on the unit circle to its center. Aiding in this process is the arctangent; JavaScript's atan2 function streamlines this computation. Essentially, it provides the angle between your current position and a reference point. The output is in radians and should be converted to degrees. (https://en.wikipedia.org/wiki/Atan2)

In a simplified scenario with flat coordinates, the process would resemble:

var deltaX = poi.x - you.x;
var deltaY = you.y - poi.y;
var rotation = toDeg(Math.atan2(deltaX, deltaY));

bearingElement.css('transform', 'rotate(' + rotation + 'deg)');

Here, poi denotes the Point of Interest, while you represents your location.

To adjust for your own rotation, subtract your current rotation angle. In the previous snippet, it would appear as follows:

var deltaX = poi.x - you.x;
var deltaY = you.y - poi.y;
var rotation = toDeg(Math.atan2(deltaX, deltaY));

rotation -= you.rotation;

bearingElement.css('transform', 'rotate(' + rotation + 'deg)');

A sample Plunckr has been created to showcase a basic flat coordinate system. By moving and rotating 'you' along with the 'point of interest,' observe how the arrow within 'you' consistently aims towards the 'poi,' despite 'you' rotating. This compensates for our rotational adjustments.

https://plnkr.co/edit/OJBGWsdcWp3nAkPk4lpC?p=preview

In the Plunckr demonstration, note that the 'zero' orientation always points north. Verify your application to determine your 'zero' alignment. Adjust accordingly to ensure script functionality.

Trust this elucidation proves beneficial :-)

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Make sure that the click event listener is set up on the anchor element so that it also affects its children

Currently, I have implemented a click event listener on my anchor elements. However, the anchors contain a span element within them, and the event listener does not function properly if you click on the span inside the anchor. document.addEventListene ...

Can you please share your methods for fetching and caching paginated collections, particularly within the context of Angular or Restangular?

In an app that retrieves data from a REST api with paginated collections of potentially infinite size, how can we effectively handle fetching, caching, and invalidating this data? Imagine the API endpoint is: GET /users?page=1&per_page=50 The respon ...

Exploring the DOM: A Guide to Observing the Present State with React Testing Library

Currently, I am delving into React Testing Library, drawing from my extensive TDD experience in various programming languages. The documentation for React Testing Library mentions that if getByText fails, it will "print the state of your DOM under test" h ...

Tips for embedding a PHP function within JavaScript code

I am working on enhancing an online application with a translation feature. The application comprises of a frontend coded in HTML and JS, and a backend developed using PHP that is linked to a database. Communication between the frontend and backend occurs ...

Attempt to resend email if unsuccessful

I have implemented nodemailer in my node application for sending emails. However, I am encountering an issue where the email sometimes fails to send and generates an error message. Typically, it takes two or three attempts before the email is successfully ...

What are the steps to resolve an invalid token error when receiving it? (repl.it)

Today marks my introduction to node.js, recommended by a friend. Due to limited resources, I have opted to utilize repl.it for hosting. However, I am faced with an error in my first attempt at using it. The purpose of my current script is to make my user ...

Guide on implementing asyncWithLDProvider from Launch Darkly in your Next.js application

Launch Darkly provides an example (https://github.com/launchdarkly/react-client-sdk/blob/main/examples/async-provider/src/client/index.js) showcasing how to use asyncWithLDProvider in a React project (as shown below). However, I'm struggling to integr ...

Is there a way to dynamically adjust height from 0 to 'auto' using @react-spring useTransition?

When utilizing the useTransition hook to incorporate animation into a list element, I encountered an issue where the height of its child elements is not fixed. If I specify {from:{height:0},enter:{height:'auto'}, the animation effect is lost. Is ...

Retrieve information from deeply nested JSON and showcase using Vue-Multiselect

My goal is to fetch data from the server and present it in Multiselect using nested JSON, which can be done through Vue-Multiselect. Once displayed, I should have the ability to add new tags as needed, essentially updating the data. While I can display o ...

Transferring data only once specific agreements have been fulfilled

Imagine having a file with specific promises that, when executed sequentially, create an input file called input.txt. // prepareInput.js var step1 = function() { var promise = new Promise(function(resolve, reject) { ... }); return p ...

Having difficulty locating the login button on the webpage

I am attempting to log into a banking account using selenuim. After opening the webpage and locating the login element, I initially struggled to access it by its "name" or "id." Fortunately, I was able to successfully access it using driver.find_element_by ...

What could be the reason for the absence of the HTTP response in the network tab of the Chrome debugger?

After utilizing Postman to test my web service, I was able to find the WS's response in the http response body. However, when I made a call using ajax in my web application, I encountered an issue where I could no longer locate the response. The tab ...

What is the best way to pass an array to a child component in React?

I am having an issue where only the first element of inputArrival and inputBurst is being sent to the child component Barchart.js, instead of all elements. My goal is for the data to be instantly reflected as it is entered into Entrytable.js. EntryTable.js ...

Is there a way to retrieve text content from a modal using JavaScript?

I am using ajax to fetch values from my table. $(data.mesIzinTanimList).each(function (index, element) { if (element.izinTanimiVarmi == 'yok') tr = "<tr class='bg-warning text-warning-50' row='" + element. ...

After a certain time has passed, an event will occur once a div element has been assigned

Is there a way to show div2 only after div1 has been given the '.selected' class for a set duration, and then hide it again when div1 loses the '.selected' class? What would be the most efficient approach to achieve this? ...

Once the window width exceeds $(window).width(), the mouseenter function is triggered exponentially

My side menu smoothly folds to the right or left based on the size of the browser window. However, I noticed that upon first reload, the mouseenter and mouseleave events are triggered twice for each action. While this isn't a major issue, it becomes p ...

Adding Options on the Fly

I am struggling with integrating option elements into optgroups within my HTML structure using JavaScript, particularly while utilizing the jQuery Mobile framework. Below is the initial HTML setup: <form> <div class="ui-field-contain"> ...

Relocate the form element from its current position inside the form, and insert it into a different div

I have a sidebar on my website with a search/filter form. I am attempting to relocate one of the elements (a sort order dropdown) from this form, outside the form using CSS/JS and display it inside a div on the right side. Is it possible to do this without ...

The perfect method for creating template literals using a function

I have a function that accepts an argument called id and returns a template literal: const generateTemplate = (id) => { return `<div style="box-sizing: border-box; height: 32px; border-bottom: 1px solid #ECECEC; color: #282828; padding: 8px; dis ...

Progress of file uploads on the client side using Vue JS

I'm facing a challenge in creating a loading bar that shows the progress when a file is selected for upload in a form. Most of the examples I come across online demonstrate the loading bar after hitting an 'upload' button, but in my case, I ...