Pull to refresh

Компас, указывающий не на север, или как мне пригодилась тригонометрия

Development for iOSObjective C
Sandbox
Привет, Хабр!

Я занимаюсь разработкой под iOS и недавно передо мной встала задача – создать компас, который указывает направление не на север, а на определенную точку земли. Это конечно не совсем компас, но за неимением лучшего названия, буду называть его так.

Наша компания занималась разработкой мобильного помощника для мусульман, частью которого и был этот самый компас. Его задача – указывать направление на Мекку. Подобная функция имеется практически в каждом подобном приложении, так что идея далеко не новая, однако над реализацией пришлось поломать голову.

Написание обычного компаса на objective-c – задача несложная, и можно найти множество примеров его реализации. Нужно с помощью класса CLLocationManager получить текущее значение heading, которое показывает, на сколько градусов текущее направление устройства отклонено от направления на север (далее просто heading). Затем взять этот угол, умножить на -1, перевести в радианы, и повернуть картинку с компасом на получившейся угол. То есть, на сколько повернуто устройство, на столько же мы поворачиваем картинку с копмасом, только в обратном направлении.

В моем случае, мне тоже нужно было повернуть картинку на определенный угол, только отклонение вычислять не относительно севера, а относительно Мекки. Таким образом задача сводилась к вычислению угла, который показывает, насколько текущее направление устройства отклонено от направления на Мекку. При этом, входными параметрами являлись текущие координаты устройства, координаты Мекки и значение heading. Поразмыслив немного, я решил, что неплохо было бы представить все это ввиде прямоугольной системы координат, с двумя точками на ней, где ось абсцисс – это значения долготы, а ось ординат – широты.



И действительно, в таком виде задача выглядит намного проще! Теперь можно свести все это к вычислению одного из углов прямоугольного треугольника. Нас интересует угол, прилежащий к катету, который параллелен оси ординат (В). Итак, мы легко можем вычислить стороны А и В, а затем и сторону С. Угол α можно вычислить через арксинус отношения стороны А к стороне С.



Отлично! Но полученный угол еще не является решением поставленной задачи. Нужно произвести некоторые дополнительные вычисления, причем они зависят от того, в какой четверти относительно Мекки находится пользователь. Мы хотим вычислить угол (обозначен оранжевым цветом) между направлением на Мекку и направлениям на север при текущем местоположении. Так, в ситуации № 1 (если пользователь к северо-востоку от Мекки), нужно прибавить к углу α 180 градусов. В ситуации № 2 (к юго-востоку от Мекки) – из 360 вычесть угол β. В ситуации № 3 (к юго-западу от Мекки) угол γ уже является искомым углом. В ситуации № 4 (к северо-западу от Мекки) нужно из 180 вычесть угол δ.



Итак, мы вычислили угол между направлением на Мекку и направлениям на север. Так как устройство уже отклонено на какой-то угол относительно севера (heading), нужно из heading вычесть вычисленный нами угол. Теперь наконец мы имеем угол, на который нам нужно повернуть нашу картинку с компасом (его еще нужно умножить на -1 и перевести в радианы).

Вот пример метода, который получает координаты и значение heading, и возвращает нужный нам угол:

- (double)angleFromLocation:(CLLocationCoordinate2D)coordinate andHeading:(double)heading
{
    double ourLatitude = coordinate.latitude;
    double ourLongitude = coordinate.longitude;
    
    // вычисляем катеты и гипотенузу
    double cathetusA = ABS(ourLongitude - MEKKA_LON);
    double cathetusB = ABS(ourLatitude - MEKKA_LAT);
    double hypotenuse = sqrt(cathetusA * cathetusA + cathetusB * cathetusB);
    
    // вычисляем угол α
    double angle1 = asin(cathetusA/hypotenuse) * 180/M_PI;
    double angle2; // насколько направление на Мекку отклонено от направления на север
    double angle3; // результат
    
    // перебираем все возможные варианты расположения относительно Мекки, и производим дополнительные вычисления
    if (ourLatitude > MEKKA_LAT) {
        if (ourLongitude > MEKKA_LON) {
            angle2 = 180.0 + angle1;
        }
        else if (ourLongitude < MEKKA_LON) {
            angle2 = 180.0 - angle1;
        }
        else {
            angle2 = 180.0;
        }
    }
    else if (ourLatitude < MEKKA_LAT) {
        if (ourLongitude > MEKKA_LON) {
            angle2 = 360.0 - angle1;
        }
        else if (ourLongitude < MEKKA_LON) {
            angle2 = angle1;
        }
        else {
            angle2 = 0.0;
        }
    }
    else {
        if (ourLongitude > MEKKA_LON) {
            angle2 = 270.0;
        }
        else if (ourLon < MEKKA_LON) {
            angle2 = 90.0;
        }
        else {
            angle2 = 0.0;
        }
    }
    
    angle3 = heading - angle2; // насколько утсройство отклонено от направления на Мекку
    angle3 = -angle3 * M_PI / 180.0f; // на какой угол нужно повернуть картинку компаса
    return angle3;
}


В итоге все работает! Можно использовать координаты различных городов мира и убедиться, что направление вычисляется правильно. Добавив пару дизайнерских изысков, получили довольно интересный компас:



Мне интересно, существуют ли иные способы решения этой задачи? Я надеюсь, что пост получился интересным и полезным.

Буду рад вашим комментариям.
Tags:ios developmentтригонометрияobjective-c
Hubs: Development for iOS Objective C
Rating -4
Views 10.3k Add to bookmarks 29
Comments
Comments 22

Popular right now

iOS-разработчик (Obj-C/Swift)
from 120,000 to 190,000 ₽4Taps MobileТольятти
iOS-разработчик
from 110,000 to 150,000 ₽InstreamaticТулаRemote job
IOS разработчик (junior)
from 100,000 to 120,000 ₽ЧИТАЙ-ГОРОДМосква
iOS Разработчик
to 250,000 ₽IvideonМоскваRemote job
Middle iOS Developer
from 150,000 to 200,000 ₽HighTeamМосква

Top of the last 24 hours