Создание WMS сервера на базе GRASS GIS и Pyramid
В данной статье описывается простейший пример создания WMS сервера на базе геоинформационной системы GRASS. Доступ к данным организован при помощи фреймворка Pyramid.
Данная статья является расширенным переводом с английского языка статьи GRASS GIS Web Map Service with Pyramid.
Общие сведения
Статья расчитана на читателя, который на базовом уровне знаком с геоинформационной системой GRASS и языком программирования Python. В частности, читатель должен представлять, как происходит вызов команд GRASS на языке Python и быть знакомым с основами использования фреймворка Pyramid.
В статье испольуется GRASS версии 6.4 (тем не менее, изменения в приведенном коде для GRASS 7 должны быть минимальными).
Архитектура системы
В основе WMS сервера будет лежать модуль d.mon, отвечающий за отрисовку геоданных. При этом будет использоваться графический драйвер Cairo, который позволяет генерировать изображения в форматах PNG, BMP, PPM, PS, PDF и SVG.
Для реализации сервера потребуется создать функции, служащие для:
- Настройки путей к GRASS.
- Получения и анализа параметров запроса WMS.
- Отрисовки слоев по запросу клиента и сохранения результата в графический файл.
- Возвращения результата обработки запроса клиенту.
Собственно к GRASS относятся первый и третий пункты, остальные --- обычные действия, которые реализуются при помощи Pyramid и с GRASS никак не связаны. Поэтому для простоты изложения и реализации объединим первый и третий пункты в виде одной функции.
Реализация
Настройка путей GRASS и обработка запроса клиента
Ниже приводится код функции, которая инициализирует GRASS, отрисовывает запрошенные пользователем слои и сохраняет полученное изображение во временном файле.
На вход функция принимает список слоев (layers), которые требуется отрисовать, охват интересующего пользователя региона (bbox) и ширину/высоту (width/height) изображения. На выходе функция возвращает имя файла, в котором будет сохранено запрошенное изображение.
def _grass_wms(layers=[], bbox=[], width=256, height=256): # Прописываем пути к GRASS. Исправить под свои нужды. gisdbase = "/home/username/GRASSDATA" location = "Moscow" mapset = "PERMANENT" gisbase = os.environ["GISBASE"] = "/usr/lib64/grass-6.4.3" # Добавляем путь модулям GRASS в системные пути sys.path.append("%s/etc/python" % gisbase) # Импортируем библиотеки GRASS from grass.script import core as grass from grass.script import setup as gsetup # Инициализируем GRASS gsetup.init(gisbase, gisdbase, location, mapset) # Получаем список известных растровых и векторных слоев vector_layers = grass.list_strings("vect") raster_layers = grass.list_strings("rast") grass.run_command("g.region", w=bbox[0], s=bbox[1], e=bbox[2], n=bbox[3]) # Создание временного файла tempfile = grass.tempfile() filename = "%s.png" % tempfile grass.run_command("d.mon", start="cairo", width=width, height=height, output=filename) for layer in layers: if layer in raster_layers: grass.run_command("d.rast", map=layer, quiet=1) elif layer in vector_layers: grass.run_command("d.vect", map=layer, quiet=1, fcolor="0:0:255", color=None) grass.run_command("d.mon", stop="cairo") return filename
Как видим, большая часть кода связана с первоначальной настройкой GRASS. Основной код по отрисовке запрошенных слоев использует команды GRASS занимает пять строк:
for layer in layers: ...
При этом все векторные слои будут отрисованы синим цветом, (команда grass.run_command("d.vect", map=layer, quiet=1, fcolor="0:0:255", color=None)). Внешний вид слоя может быть изменен настройкой параметров команды d.vect, например, можно назначить стиль отображения объекта в зависимости от его атрибутов или геометрических характеристик.
Аналогично, растровые слои отображаются в той цветовой схеме, которая была им назначена ранее. При необходимости изменить стиль слоя следует воспользоваться командами, обрабатывающими цветовые схемы растровых карт, например r.colors или др.
Анализ запроса пользователя и возвращение результата
Анализ запроса пользователя производится в следующей функции:
@view_config(route_name="wms") def wms_view(request): layers = request.params.get("LAYERS", "").split(",") bbox = request.params.get("BBOX", "").split(",") width = request.params.get("WIDTH") height = request.params.get("HEIGHT") try: filename = _grass_wms(layers=layers, bbox=bbox, width=width, height=height) f = open(filename, "r+") return Response(body=f.read(), content_type="image/png") except: pass
Данная функция принимает запрос (request), в котором могут определяться:
- список слоев в запрашиваемом изображении (параметр LAYERS);
- охват области запрашиваемого изображения (параметр BBOX);
- высота и ширина запрашиваемого изображения (параметры HEIGHT и WIDTH).
Полученные параметры анализируются и передаются в определенную выше функцию _grass_wms. Результат работы функции --- имя файла изображения --- считывается с диска и возвращается в качестве ответа на запрос.
Создание индексного файла
Еще одно действие --- создание простейшего веб-приложения, которое использует построенный WMS сервер. Воспользуемся библиотекой OpenLayers для отображения карты, возвращаемой сервером.
Данный шаг является необязательным, но его удобно реализовать в целях отладки.
Следующая функция создает веб-страницу с картой на базе слоев векторных слоев mo и io_15:
@view_config(route_name="index") def index_view(request): body = <html xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal" xmlns="http://www.w3.org/1999/xhtml"> <head> <title>GRASS GIS Web Map Service</title> <script src="http://openlayers.org/api/OpenLayers.js" type="text/javascript"></script> <script type="text/javascript"> var map, wms_layer; function init(){ var map = new OpenLayers.Map("map-div",{ projection: "EPSG:28407", maxExtent: new OpenLayers.Bounds(7361000, 6115000, 7435000, 6212000), numZoomLevels: 6, }); var wms_layer = new OpenLayers.Layer.WMS("GRASS WMS", '/wms', { layers: "mo,io_15" },{ singleTile: true, }); map.addLayer(wms_layer); map.zoomToMaxExtent(); } </script> </head> <body onload="init()">
< div id="map-div" style="height: 600px; width: 800px;">
</body> </html> return Response(body=body, content_type="text/html", status=200)
Конфигурирование Pyramid
Для того, чтобы описанные выше функции можно было использовать, необходимо сконфигурировать Pyramid. Пример такой функции приведен ниже:
def main(global_config, ** settings): """ Данная функция возвращает WSGI-приложение Pyramid. """ config = Configurator(settings=settings) config.add_static_view('static', 'static', cache_max_age=3600) config.add_route("index", '/') config.add_route("wms", '/wms') config.scan() return config.make_wsgi_app()
Ограничения данной реализации
Рассмотренный пример демонстрирует принцип создания WMS сервера, потому в нем опущен ряд деталей, которые должны быть реализованы в реальной системе. Например, в рассмотренном варианте присутствуют следующие ограничения:
- Нет перпроецирования "на лету". Т.е. данные WMS сервер может возвращать слои только в той проекции, в которой хранятся данные GRASS.
- Реализация не является потокобезопасной. Например, при одновременном запросе данных из разных областей GRASS могут проявляться неожиданные побочные эффекты.