Несколько ранее мы рассматривали возможность публикования растра в Веб
различными способами, как в виде WMS так и виде TMS:
* MapServer + MapProxy
* gdal2tiles.py
Все эти инструменты так или иначе используют библиотеку GDAL. Посмотрим каким
образом мы можем использовать ее напрямую из python (пакет `python-gdal`).
И в качестве примера попробуем реализовать простой TMS-подобный сервис на основе
растрового файла.
Структура директорий:
gdaldemo
/data
test.tif
/static
map.html
tms.py
Получение информации о растрах
------------------------------
Для начала попробуем получить часть той информации о растре, которую показывает
утилита `gdalinfo`.
Открываем набор данных в режиме только для чтения.
from osgeo import gdal, gdalconst
ds = gdal.Open('../data/test.tif', gdalconst.GA_ReadOnly)
После того, как набор данных открыт, получим основные геометрические параметры
изображения:
geotransform = ds.GetGeoTransform()
print "GeoTransform: %s" % repr(geotransform)
* X-координта левого верхнего угла изображения
* Разрешение по X
* 0 для изображений, у которых север наверху
* Y-координата левого верхнего угла изображения
* 0 для изображений, у которых север наверху
* Разрешение по Y
Ширина и высота изображения:
width, height = ds.RasterXSize, ds.RasterYSize
print "Width: %d; Height: %d" % (width, height)
Выясним какой драйвер используется для нашего изображения:
print "Driver: %s (%s)" % (
ds.GetDriver().ShortName,
ds.GetDriver().LongName
)
Количество каналов:
print "RasterCount: %d" % ds.RasterCount
Примитивный TMS-подобный сервис
-------------------------------
Реализуем веб-сервис, который по URL вида `/z/x/y` отдает PNG-тайлы. В качетсве
веб-фреймворка возьмем [Flask](http://flask.pocoo.org/), в первую очередь из-за
своей компактности (пакет `python-flask`).
Из этого фреймворка нам понадобится несколько вещей:
from flask import Flask, send_file
app = Flask(__name__)
@app.route('///')
def home(z, x, y):
# Дальнейший код пишем тут
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
Процедура `home` перехватывает ссылки вида `/z/x/y`. Приведем значения
переменных в числам:
z, x, y = map(int, (z, x, y))
Вычислим экстент запрашиваемого тайла:
MAXCOORD = 20037508.34
step = 2 * MAXCOORD / 2 ** z
extent = (
- MAXCOORD + x * step, MAXCOORD - (y + 1) * step,
- MAXCOORD + (x + 1) * step, MAXCOORD - y * step,
)
Создаем результирующее изображение 256 на 256 пикселов (необходим пакет
`python-imaging`).
from PIL import Image
result = Image.new("RGBA", (256, 256), (0, 0, 0, 0))
Пересчитываем координаты в пиксели исходного растра:
off_x = int((extent[0] - geotransform[0]) / geotransform[1])
off_y = int((extent[3] - geotransform[3]) / geotransform[5])
width_x = int(((extent[2] - geotransform[0]) / geotransform[1]) - off_x)
width_y = int(((extent[1] - geotransform[3]) / geotransform[5]) - off_y)
Проверяем, чтобы запрашиваемые пиксельные координаты целиком попадали
в наше изображение, и если это не так, то рассчитываем необходимые поправки:
target_width, target_height = (256, 256)
offset_left = offset_top = 0
# правая граница
if off_x + width_x > ds.RasterXSize:
oversize_right = off_x + width_x - ds.RasterXSize
target_width -= int(float(oversize_right) / width_x * target_width)
width_x -= oversize_right
# левая граница
if off_x < 0:
oversize_left = -off_x
offset_left = int(float(oversize_left) / width_x * target_width)
target_width -= int(float(oversize_left) / width_x * target_width)
width_x -= oversize_left
off_x = 0
# нижняя граница
if off_y + width_y > ds.RasterYSize:
oversize_bottom = off_y + width_y - ds.RasterYSize
target_height -= round(float(oversize_bottom) / width_y * target_height)
width_y -= oversize_bottom
# верхняя граница
if off_y < 0:
oversize_top = -off_y
offset_top = int(float(oversize_top) / width_y * target_height)
target_height -= int(float(oversize_top) / width_y * target_height)
width_y -= oversize_top
off_y = 0
Выгружаем необходимый фрагмент изображения в массив numpy (необходим пакет
`python-numpy`), из которого затем формируем изображение и размещаем его на
тайле в соответствии с полученными поправками:
from osgeo import gdal_array
import numpy
if target_width <= 0 or target_height <= 0:
pass
else:
band_count = ds.RasterCount
array = numpy.zeros(
(target_height, target_width, band_count),
numpy.uint8
)
for i in range(band_count):
array[:, :, i] = gdal_array.BandReadAsArray(
ds.GetRasterBand(i + 1),
off_x, off_y,
width_x, width_y,
target_width, target_height
)
wnd = Image.fromarray(array)
result.paste(wnd, (offset_left, offset_top))
И, наконец, отправляем картинку клиенту:
buf = StringIO()
result.save(buf, 'png')
buf.seek(0)
return send_file(buf, mimetype='image/png')
Клиентская часть
----------------
Клиентскую часть нашего приложения реализуем на базе библиотеки Leaflet, которая
так же как OpenLayers предначена отображени веб-карт на стороне клиента.
В нашем случае клиентская часть состоит из одного файла `static/map.html`:
После того, как мы запустим наше приложение, страница с картой будет доступна
по адресу [http://127.0.0.1:5000/static/map.html](http://127.0.0.1:5000/static/map.html).
$ python tms.py
GeoTransform:(4412916.6550928205, 41.91620792588408, 0.0, 5608060.691815066, 0.0, -41.91620792588408)
Width: 3036; Height: 5157
Driver: GTiff (GeoTIFF)
RasterCount: 3
* Running on http://0.0.0.0:5000/
* Restarting with reloader
GeoTransform:(4412916.6550928205, 41.91620792588408, 0.0, 5608060.691815066, 0.0, -41.91620792588408)
Width: 3036; Height: 5157
Driver: GTiff (GeoTIFF)
RasterCount: 3
![][06-gdal-01]
[06-gdal-01]: ../img/06-gdal-01.png