Домой О курсе Несколько ранее мы рассматривали возможность публикования растра в Веб различными способами, как в виде 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('/<z>/<x>/<y>') 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`: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.5/leaflet.css" /> <script src="http://cdn.leafletjs.com/leaflet-0.5/leaflet.js"></script> <style type="text/css">#map { height: 400px; width: 600px; }</style> </head> <body> <div id="map" /> <script type="text/javascript"> var map = L.map('map').setView([44.2185895, 40.2134582], 12); L.tileLayer('http://tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 18 }).addTo(map); L.tileLayer('/{z}/{x}/{y}', { maxZoom: 18 }).addTo(map); </script> </body> </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