Animated directional arrows "aroundMe" style using ngCordova
Asked Answered
A

1

37

I wish to create the compass / arrow exactly like the one we see in AroundMe Mobile App that point exactly to a pin to the map accordingly with my mobile position and update the arrow when I move the phone.

I'm getting crazy to understand exactly how to do that and I can not find any guide or tutorial that explain a bit it.

What I found online is a bearing function and I created a directive around it:

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)');
    }
  };

});

It seems to update the arrow in a direction but unfortunately it is not the right direction because it is not calculated using also the mobile magneticHeading position.

So I added the ngCordova plugin for Device Orientation to get the magneticHeading and now I don't know exactly how to use it and where in the 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 tried to add it in the return statement:

return (brng - heading) % 360;

or:

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

Implementing this code with a watcher I see the arrow moving but not in the exact position... For example from my position and the pin the arrow should point to N and it is pointing to E.

Always looking online I can not find any tutorial / question to find the bearing between a lat/lng point and a magnetingHeading.

Maybe I'm close to the solution but I can not go ahead alone.

I also tried to search for a mathematical formulas but even there is a huge pain to understand and implement it.

I hope you can help.

Accumulative answered 10/2, 2016 at 14:28 Comment(6)
I don't know if this plunker could help you: plnkr.co/edit/WC9Kspe54tlod5EOpndDDreiser
@Dreiser thanks for your comment. It is made using just latitude and longitude, not using the mobile position... :(Accumulative
Maybe this post could help you... or this oneDreiser
@AyeyeBrazo you may prefer this link and hope you will found your solution : forum.ionicframework.com/t/compass-rotation-with-ionic/14517Riggins
navigator.geolocation.watchPosition(function(position){ var heading = position.coords.heading;});Gratiana
When it always point to E why not subtract 90° like return (brng - heading - 90) % 360;Casto
G
1

It's hard to give a plain answer to this question because a lot depends on the actual graphical representation. For instance, in what direction do you point where rotate(0deg).

I can explain the formula you've found, which might help you to clear the issue yourself. The hard part is 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);

What you see here is Haversines formula (https://en.wikipedia.org/wiki/Haversine_formula). Normally one could suffice with two sets of coordinates and calculate the angle between them. When working with latitude and longitude this will not work because the earth is not a flat surface. The first three lines are Haversine and the result (x and y) are coordinates on the unit circle (https://en.wikipedia.org/wiki/Unit_circle)

The next step is to calculate the angle from the point on this unit circle to the center. We can use the arctangant for this. Javascript has a helper function atan2 which simplifies this process. The result is simple the angle of your point towards the center of the circle. In other words, your position towards your point of interest. The result is in radians and needs to be converted to degrees. (https://en.wikipedia.org/wiki/Atan2)

A simplified version with a flat coordinate system would look like this:

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)');

Where poi is the Point of Interest and you is your position.

To compensate for your own rotation, you need to substract your own rotation. In the above sample the result would become:

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)');

I've made a Plunckr in which you can see a simple flat coordinate system. You can move and rotate you and the point of interest. The arrow inside 'you' will always point towards the poi, even if you rotate 'you'. This is because we compensate for our own rotation.

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

Note in the Plunckr that the 'zero'-position is always to the north. Check your app to see your 'zero'-position. Adjust it accordingly and your script will work.

Hope this helps :-)

Gallicanism answered 22/9, 2016 at 21:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.