0

Как сделать карту примечательностей Ижевска под iPhone?

Данил Смакотин
24 ноября 2011 года
Сегодня я расскажу о процессе разработки приложения “Примечательности Ижевска” (оно же iИжевск) для iPhone, в котором участвовало много хороших людей и один из них — это я — человек, который занимался непосредственно разработкой, поэтому мой рассказ будет посвящен в основном этой фазе проекта. Текста будет много, поэтому всех неравнодушных прошу пожаловать под кат.

Выбор средств разработки

Изначально выбор у iOS-разработчика небольшой. Использовать:

  1. Objective-C в качестве языка программирования и iOS-SDK в качестве набора различных инструментов
  2. Один из фреймворков, большинство из которых используют JavaScript, HTML и CSS

Подробнее постараюсь рассказать о них в одном из следующих постов, а пока с помощью сравнительной таблицы в английской википедии вы можете сами прийти к выводу, что для разработки iOS-приложений самый подходящий из них — Appcelerator Titanium. В частности, потому что с его помощью можно делать именно встроенные, а не веб-приложения, и потому что классы и методы, которые предлагаются разработчику являются не симуляцией нативых аналогов при помощи движка WebKit, как это делает, к примеру, PhoneGap, а самыми что ни на есть JS-обертками милых сердцу аналогов из Objective-C. Также у разработчика имеется возможность расширения Titanium путем написания собственных модулей на Objective-C (или на Java для Android). К сожалению, в Titanium реализованы в большинстве своем наиболее типичные классы и методы. В нашем случае задача была весьма творческой и потребовала бы написания довольно-таки объемного модуля, возможно даже более, чем одного. В итоге, мы получили бы приложение не самое быстрое и, наверняка, плохо расширяемое. Таким образом, выбор был ограничен рамками Objective-C и iOS SDK.

Карта наша очень красива и своеобразна, поэтому далеко не факт, что получится реализовать ее средствами стандартного фреймворка от Apple — MapKit, в основе которого лежат Google Maps. Второй вариант — использовать библиотеку RouteMe с открытым исходным кодом. Ну и третий — написать карту с нуля самим.

  1. MapKit позволяет использовать пользовательские наложения на карту, начиная с iOS 4.0, хотя, конечно, скорее всего разработчики предполагали их использование для чего-то другого, например, наложения маршрутов (где-то в лицензионном соглашении использования Google Maps можно это уточнить, хотя, конечно, уточнить могут это и работники Apple, когда будут писать вам, по какой причине они отклонили ваше приложение). Тем не менее, теоретически, задача решаема с помощью MapKit. Но тут встает ряд других вопросов, среди которых:
    • Можно ли заставить работать Google Maps полностью в offline-режиме (зачем нам подгружать под нашу красивую карту какую-то другую?)
    • Можно ли ограничить карту пределами Ижевска?

    Решения, безусловно, можно найти. Но может стоить рассмотреть другие варианты?

  2. Многие рекомендуют использовать библиотеку RouteMe, которая позволяет помимо Google Maps использовать также OpenStreetMaps и ряд других движков для карт. Данный вариант подходит уже гораздо лучше. Однако, у него есть и свои минусы. В частности, на момент реализации приложения библиотека не поддерживала offline-вариант работы. Сейчас это возможно при использовании DBMap и MBTiles. Ну и поскольку библиотека разрабатывается opensource-сообществом, то она далеко не идеальна, хоть и развивается довольно-таки активно. Возможно, данный вариант сегодня имеет смысл использовать
  3. В итоге, было принято решение написать все с нуля. Подробнее об этом варианте ниже, сразу после описания подготовительной работы.

Подготовительная работа

Очень хорошо, когда все участники проекта работают в одной компании. Это отличный повод показать друг другу, какое в компании замечательное взаимодействие между людьми. В данном случае речь идет о менеджере, дизайнере, разработчике интерфейса и программистах под две платформы (iOS, Android).

И не откладывая дело в долгий ящик, я попросил нашего дизайнера и идейного вдохновителя проекта, Ксению Докучаеву, сделать удобный psd-файл: сделать отдельные слои для названий крупных улиц и улиц поменьше, парков, стадионов и прочих прудов; отдельную папку с отдельным слоем для каждой достопримечательности и адекватным ей названием. Ксюша любезно сделала небольшой (чуть более 500 мегабайт) psd-файл, в котором лежала карта, совпадающая по своим пропорциям с экраном iPhone (таким образом, Ижевск подвергся айфонизации). Далее был написан скрипт на JScript, с помощью IDE Adobe ExtendScript Toolkit (входит в дистрибутив Photoshop) и позволяет работать с API Photoshop, в котором покрыты многие из фотошоповских функций. Об этом есть статья на хабре. Скрипт вычислял центры координат иконок для достопримечательностей (видимых слоев) и нормировал координаты по осям x и y, заключая их область определения в отрезок [0, 1]. В базе хранились целые значения. Поскольку при наибольшем увеличении масштаба карты ее размер не превосходит 10000 пикселей в обоих направлениях, то эти целые значения получились умножением нормированных координат на 10000:
<layer name="Не кантовать" coord_x="8924" coord_y="3115"/>
После чего все это попадало в базу SQLite (которая использовалась прежде всего потому, что является универсальным хранилищем данных для iOS и для Android).

Поскольку, ресурсы iPhone, как и других мобильных устройств значительно уступают ресурсам таких компьютеров, как, к примеру, iMac, единственный способ отображать карту быстро — это разрезать ее на множество кусков и сменять их по мере необходимости, что и делает Google Maps, но в отличие от их 20 уровней детализации, Ижевску хватило всего 4-х. В пикселях:

  1. 512*768
  2. 1024*1536
  3. 2048*3072
  4. 4096*6144

Изображения были разрезаны инструментом TileCutter на т.н. тайлы (плитки) , размером 256*256 пикселей. Полученные изображения были названы в соответствии со следующим форматом: уровень увеличения_номер столбца_номер строки. Все они были добавлены к ресурсам приложения и заняли не так много места: чуть более 6 мегабайт.

Разработка

Карта

Основа карты была реализована при помощи класса CATiledLayer, который входит в состав фреймворка QuartzCore. Класс очень полезен для работы с изображениями высокого разрешения. Механизм работы примерно следующий:

  1. Создаем основу для нашей карты — это экземпляр класса UIView, который завернут в экземпляр UIScrollView
  2. Переопределяем метод класса layerClass:
    + layerClass {
    return [CATiledLayer class];
    }
  3. Задаем 4 уровня детализации для карты:
    tiledLayer.levelsOfDetail = 4;
  4. Переопределяем метод - (void)drawRect:(CGRect)rect экземпляра UIView таким образом, что в зависимости от прямоугольника rect отрисовываются нужные тайлы. Прелесть CATiledLayer заключается в том, что благодаря ему все это происходит ассинхронно, причем CATiledLayer сам решает, сколько необходимо запустить потоков, а также кэширует изображения для нескольких уровней детализации. В итоге все работает очень быстро! И даже на iPhone 3G.Более подробно о классе можно почитать в документации Apple. А сам подход был отлично описан на WWDC 2010. Видео доступно для скачивания в iTunes, там можно включить английские субтитры. Код проекта доступен в Sample Code, который в новом Xcode 4 можно загружать прямо из раздела документации в органайзере. Также этому подходу посвящен пост в блоге cocoa is my girlfriend.
    Когда карта была готова, это показалось очень крутым, но самое интересное было впереди.

Иконки, всплывающие подсказки

Сложность отрисовки иконок и подсказок заключалась в том, что они лежали в иерархии на самом верхнем уровне. То есть на том же, что и сама карта, иначе бы они масштабировались бы при изменении масштаба карты, что нам совсем не нужно. Таким образом место действия развернулось, условно говоря, в трех системах координат:

  1. Система координат карты с началом координат в левом нижнем углу. Максимальные значения: по x — 4096, по y — 6144: 
  2. Система координат UIScrollView. Начало координат там же, максимальные значения зависят от текущего масштаба: 
  3. Система координат экрана. Начало в левом нижнем углу экрана. Максимальные значения в пределах экрана: 320 и 480, в пределах карты: 4096 и 6144. Соответственно минимально возможные: 320-4096 и 480-6144: 

Таким образом, вся математика основана на преобразовании координат между этими тремя системами. В делегатных методах мы запускаем код, который при каждом движении UIScrollView пересчитывает координаты иконок, попадающих в экран и подсказки, если она активна:

-(void)scrollViewDidZoom:(UIScrollView *)pScrollView {
[mapViewController calcCoord];
}

- (void)scrollViewDidScroll:(UIScrollView *)pScrollView {
[mapViewController calcCoord];
}

Но всегда найдется какой-нибудь лось. Наш лось стоит на самом краю города и карты, поэтому при минимальном масштабе иконка в карту не влезала: 

Ради лося пришлось рассмотреть этот частный случай и хитро решить проблему. Когда пользователь уменьшает карту и иконка какой-либо достопримечательности не влезает, мы незаметно сдвигаем ее в пределы карты. С невлезающими подсказками поступили немного иначе. Решили сдвигать галочку от центра к краям:

Геолокация

Все в том же замечательном psd-файле был обнаружен еще один слой. Это был слой Яндекс карт, поэтому узнать координаты краев нашей карты было делом несложным. Правда, тестирование все-таки пришлось провести. В конце концов, прокатиться по городу на машине с iPhone — не самое утомительное занятие в жизни iOS-разработчиков.

Что дальше?

Приложение уже в appStore и уже было выпущено небольшое обновление, в котором мы немного улучшили совместимость с iOS 5. В перспективе мы планируем сделать некоторые вещи из этого списка (тут, конечно, не все, потому что мы любим делать сюрпризы):

  • Английская локализация приложения
  • Небольшое API для приема данных с сервера, что неизбежно влечет за собой появление фотогалерей у примечательностей и оперативное обновление базы данных. Сейчас приложение работает полностью offline
  • Дальнейшая работа с геолокацией: расстояние до примечательностей и прокладка маршрутов. Последнее, как и обновление базы зависит, как вы понимаете, не только от нас=)

В комментах охотно отвечу на вопросы и передам всей нашей команде ваши предложения о дальнейшем развитии карты.

Отдельная благодарность в подготовке иллюстраций нашему UX-дизайнеру и просто хорошему человеку Ивану Белобородову.

Примечательности Ижевска в портфолио ЦВТ

Метки: , ,

Оставить комментарий