Создание автономного картографического приложения на базе изображений без привязки
Рассмотрен процесс подготовки и подключения изображений без привязки в картографический JavaScript-движок Leaflet
Введение
Картографические JavaScript-движки, такие как OpenLayers или Leaflet, порой находят своё применение в таких областях, для которых они изначально вроде бы и не предназначались. Так, например, международное агентство Рейтер продемонстрировало использование Leaflet для интерактивного взаимодействия с фотографиями из зала вручения кинопремии «Оскар»:
Еще один необычный пример - интерактивный тур по городу на базе его вымышленной карты:
Таких примеров можно привести множество. Но есть один технический момент, который объединяет все эти «карты» - это то, что все они построены на базе изображений, которые не имеют абсолютно никакой географической привязки. Использование картографических движков для таких изображений добавляет возможность навигации по ним, а при небольшой предварительной обработке - возможность их масштабирования.
Именно вопросу такой предварительной подготовки и посвящена основная часть данной статьи. В качестве языка программирования будем использовать Python, в качестве JavaScript-движка - Leaflet.
Если перед вами стоит схожая задача, но вы не хотите вникать в технические моменты, тогда просто воспользуйтесь онлайн сервисом HUGEpic, принцип работы с которым заключается в том, что вы загружаете свое изображение, а на выходе получаете готовую карту, пример.
Обработка исходного изображения
Постановка задачи
Исходное изображение в принципе можно подключить "как есть", в OpenLayers для этого есть класс OpenLayers.Layer.Image, а в Leaflet - L.ImageOverlay, однако в общем случае это плохая идея, так как изображение может быть большим и пользователю придется очень долго ждать, пока оно загрузится, поэтому первое, что нужно сделать с изображением - это разбить его на тайлы. А так как мы хотим иметь возможность не только двигать наше изображение, но и изменять его масштаб, то тайлы должны быть построены для нескольких масштабных уровней.
Выбор исходного изображения
В качестве исходного изображения возьмем изображение, представляющее собой визуализацию землетрясений, произошедших, начиная с 1898 года. Подробнее об изображении тут. Размер изображения - 3410 x 2058.
Разбивка на тайлы
Переходим к написанию скрипта, который будет разбивать на тайлы наше изображение. Мы будем использовать инструменты стандартной библиотеки Python, а также одну стороннюю библиотеку - библиотеку для работы с изображениями - PIL, поэтому прежде чем переходить далее, убедитесь, что у вас установлен этот самый PIL.
Сразу оговоримся, что используемая в дальнейшем схема разбивки на тайлы - как в OpenStreetMap (то есть нумерация строк и столбцов тайлов начинается с левого верхнего угла). Алгоритм разбивки изображения на тайлы достаточно прост, но имеет некоторую особенность. Итак, представьте, что мы взяли наше изображение размера 3410 x 2058 и пытаемся разбить его на тайлы 256 x 256. Очевидно, что 3410 и 2058 не делятся без остатка на 256, а это означает, что тайлы последнего столбца и последней строки получатся не квадратными (82 x 256 и 256 x 10 соответственно). Поэтому прежде чем переходить к нарезке тайлов, изображение должно быть приведено к такому виду, что его ширина и высота будут кратны размеру тайла. Размеры поправок вычисляются следующим образом (src_width, src_height - ширина и высота исходного изображения, tile_size - размер тайла, % - операция получения остатка от деления). В Python это записывается так:
dw = tile_size - src_width % tile_size dh = tile_size - src_height % tile_size
Области расширения за счет поправок следует сделать прозрачными, в нашем коде за эту операцию будет отвечать функция adjustBounds.
Одним из важных параметров процедуры разбивки на тайлы - это максимальный масштабный уровень. Рассчитывается он как логарифм по основанию 2 числа max(w,h) (w - ширина изображения, h - высота), округленный до ближайшего большего целого. В Python это записывается так:
max_zoom = int(math.ceil(math.log((max(w, h) / tile_size), 2)))
Если посчитать, то мы получим:
max_zoom = int(math.ceil(math.log((max(3410, 2058) / 256), 2))) = 4
В итоге алгоритм разбивки на тайлы сводится к циклу
Подключение тайлов в Leaflet
{{#widget:Iframe |url=http://gis-lab.info/share/DR/public_html/non-geographical-imagery-ex.html |width=767 |height=367 |border=0 }}
Заключение
В ходе данной статьи была описана процедура разбивки изображения без привязки на тайлы, а также подключение их в Leaflet. Отметим, что операцию по тайлированию изображения можно было выполнить, используя утилиту gdal2tiles, которая входит в состав библиотеки GDAL:
gdal2tiles.py -p raster -z 0-4 earthquakes.jpg
Описанный в статье вариант решения задачи не требует использования GDAL, плюс (что самое, наверное, важное) код довольно простой и наглядно дает понимание того, как вычисляются масштабные уровни и как выполняется непосредственно сама разбивка на тайлы. По коду gdal2tiles разбираться в этих вопросах гораздо сложнее, так как это более универсальный инструмент. И еще, gdal2tiles помимо генерирования тайлов также создает html-файлы с включенными в них движками OpenLayers и Google (но не Leaflet) и подключает на карту набор созданных тайлов, то есть по-сути делает все то же самое о чем идет речь в данной статье, но как-бы за кадром, мы же попытались представить этот процесс более наглядно.