Привет!
Я занимаюсь разработкой под 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;
}
В итоге все работает! Можно использовать координаты различных городов мира и убедиться, что направление вычисляется правильно. Добавив пару дизайнерских изысков, получили довольно интересный компас:
Мне интересно, существуют ли иные способы решения этой задачи? Я надеюсь, что пост получился интересным и полезным.
Буду рад вашим комментариям.
Автор: tagirkaZ