Создание WMS сервера на базе GRASS GIS и Pyramid

Материал из GIS-Lab
Перейти к навигации Перейти к поиску

В данной статье описывается простой пример создания 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.

Для реализации сервера потребуется создать функции, служащие для:

  1. Настройки путей к GRASS.
  2. Получения и анализа параметров запроса WMS.
  3. Отрисовки слоев по запросу клиента и сохранения результата в графический файл.
  4. Возвращения результата обработки запроса клиенту.

Собственно к 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 могут проявляться неожиданные побочные эффекты.

Ссылки по теме