Встраивание кэширующего TMS-сервиса в собственное приложение: различия между версиями
Нет описания правки |
Нет описания правки |
||
(не показано 12 промежуточных версий 2 участников) | |||
Строка 1: | Строка 1: | ||
{{Статья| | {{Статья|Опубликована|tilecache-embedded}} | ||
{{Аннотация|Рассмотрена процедура встраивания TileCache в собственное приложение, а также | {{Аннотация|Рассмотрена процедура встраивания TileCache в собственное приложение, а также приведен пример создания кэша на базе PostgreSQL.}} | ||
== Введение == | == Введение == | ||
Предположим, что вы разрабатываете клиент-серверную Веб-ГИС | Предположим, что вы разрабатываете клиент-серверную Веб-ГИС. Вам требуется отображать на клиенте некоторую растровую подложку, которая создается на базе данных хранилища (БД), к которому имеет доступ серверная компонента. Стандартным решением подобной задачи является создание на сервере Tile Map Service ([http://gis-lab.info/docs/tms-specification-ru.html TMS, спецификация]) и подключение его на клиенте. | ||
отображать на клиенте некоторую растровую подложку, которая создается на базе | |||
данных хранилища (БД), к которому имеет доступ серверная компонента. | |||
Стандартным решением подобной задачи является | |||
[http://gis-lab.info/docs/tms-specification-ru.html TMS] и подключение | |||
===Виды TMS-сервисов=== | |||
TMS-сервис может функционировать в одном из двух режимов: статическом и динамическом. Статический TMS - это предварительно созданный набор тайлов, он удобен если данные, на основе которых будет создаваться TMS-слой, одинаковы для всех установок приложения (например, данные о государственных границах) и распространяются вместе с приложением. Разумеется, это может привести к недопустимому росту размера приложения и поэтому статический TMS может также предоставлять пользователю возможность генерирования тайлов "на месте", чтобы не распространять их с приложением. | |||
В качестве языка программирования будем использовать Python, | Недостаток этого варианта заключается в том, что пользователю, для того чтобы начать работать с приложением, потребуется время на создание кэша (может занимать от нескольких часов до нескольких дней). Динамической TMS решает эту проблему и генерирует тайлы по запросу. | ||
операционная система - Debian GNU/Linux 7.0. | |||
===Почему могут не подойти готовые решения=== | |||
Существующее ПО для создания динамических TMS-сервисов ([http://tilecache.org/ TileCache], [http://mapproxy.org/ MapProxy]) является самостоятельным программным обеспечением и изначально не предназначено для использования в качестве встраиваемых решений. Использовать MapProxy или TileCache по их прямому назначению может быть невозможно, если: | |||
#Пользователь по той или иной причине не может отдельно установить и настроить тайловый сервер; | |||
#Доступ к тайлам должен предоставляться не всем пользователям, а только тем, которые имеют на это права согласно подсистеме разделения прав, используемой в приложении (если таковая имеется). | |||
===Решение=== | |||
Поэтому решение данной задачи сводится к написанию собственного TMS-сервиса и интеграции его в приложение. Пример решения подобной задачи был рассмотрен в статье [http://gis-lab.info/qa/dynamic-tms.html Основы работы динамических TMS-сервисов], | |||
но основным недостатком получившегося там сервиса является то, что он не поддерживает процедуру кэширования, что очень важно при разработке реального приложения. | |||
В рамках данной статьи рассмотрим создание кэширующего TMS-сервиса на базе классов, предоставляемых TileCache. TileCache был выбран в качестве базовой системы в виду того, что он имеет довольно простую и понятную архитектуру в отличие от того же MapProxy, хотя по функционалу в целом значительно уступает последнему. В качестве рендерера будем использовать Mapnik. | |||
В качестве языка программирования будем использовать Python, операционная система - Debian GNU/Linux 7.0. | |||
== Создание каркаса приложения == | == Создание каркаса приложения == | ||
Строка 276: | Строка 254: | ||
2013-07-17 12:05:09,286 INFO [sqlalchemy.engine.base.Engine][MainThread] COMMIT | 2013-07-17 12:05:09,286 INFO [sqlalchemy.engine.base.Engine][MainThread] COMMIT | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== Создание кэширующего TMS-сервиса == | |||
Итак, все подготовительные работы закончены, переходим непосредственно к | |||
созданию сервиса. Откройте файл ~/cache/cache/cache/__init__.py и приведите | |||
его в соответствие со следующим фрагментом: | |||
<syntaxhighlight lang="python"> | |||
from pyramid.config import Configurator | |||
from sqlalchemy import engine_from_config | |||
from cache.models import DBSession | |||
from cache.models import Base | |||
def main(global_config, **settings): | |||
""" This function returns a Pyramid WSGI application. | |||
""" | |||
engine = engine_from_config(settings, 'sqlalchemy.') | |||
DBSession.configure(bind=engine) | |||
Base.metadata.bind = engine | |||
config = Configurator(settings=settings) | |||
config.add_route('tilecache', '/tilecache/{path:.*}') | |||
config.add_view('cache.tilecache.view_tilecache', route_name='tilecache') | |||
return config.make_wsgi_app() | |||
</syntaxhighlight> | |||
Данная функция обеспечивает следующую функциональность: любой запрос, имеющий | |||
URL вида ''/tilecache/*'' (где * - произвольная последовательность символов), | |||
будет обработан функцией ''cache.tilecache.view_tilecache''. | |||
Переходим к написанию этой функции. Создайте файл ~/cache/cache/cache/tilecache.py: | |||
<syntaxhighlight lang="python"> | |||
# -*- coding: utf-8 -*- | |||
import datetime | |||
import os | |||
from TileCache.Cache import Cache | |||
from TileCache.Service import Service | |||
from TileCache.Layers.Mapnik import Mapnik | |||
from pyramid.response import Response | |||
from cache.models import DBSession | |||
from cache.models import TileCache as TileCacheModel | |||
_tilecache_service = None | |||
# Весь дальнейший код будет помещен сюда | |||
</syntaxhighlight> | |||
'''Описание класса кэша PostgreSQL''' | |||
В TileCache в роли кэша может выступать любой объект класса, имеющий 2 обязательных | |||
метода: ''get'' и ''set''. Первый отвечает за извлечение данных из кэша, второй - | |||
за помещение их туда (в случае если запрошенный клиентом тайл отстутствует | |||
в кэше). Здесь же, в файле tilecache.py, описываем соответствующий класс, | |||
унаследовавшись от базового класса TileCache.Cache.Cache: | |||
<syntaxhighlight lang="python"> | |||
class PostgresCache(Cache): | |||
def __init__(self, **kwargs): | |||
Cache.__init__(self, **kwargs) | |||
def get(self, tile): | |||
dbsession = DBSession() | |||
obj = dbsession.query(TileCacheModel).\ | |||
filter_by( | |||
layer=tile.layer.name, | |||
z=tile.z, | |||
x=tile.x, | |||
y=tile.y | |||
).first() | |||
if obj: | |||
return obj.data | |||
else: | |||
return None | |||
def set(self, tile, data): | |||
dbsession = DBSession() | |||
obj = TileCacheModel( | |||
layer=tile.layer.name, | |||
z=tile.z, | |||
x=tile.x, | |||
y=tile.y, | |||
data=data | |||
) | |||
dbsession.add(obj) | |||
dbsession.flush() | |||
return data | |||
</syntaxhighlight> | |||
'''Подготовка конфигурационного XML-файла Mapnik''' | |||
При запросе клиентом тайла в случае если его нет в кэше, его нужно создать | |||
(отрендерить). Как уже было сказано выше для этой цели мы будем использовать | |||
Mapnik. | |||
В качестве источника данных воспользуемся результатами | |||
[http://gis-lab.info/qa/geodetdom.html проекта], в рамках которого был создан | |||
открытый точечный слой геоданных по детским учреждениям (детским домам). Этот слой | |||
также доступен в БД PostGIS. | |||
Создадим файл ~/cache/cache/cache/static/mapnik.xml следующего содержания: | |||
<syntaxhighlight lang="xml"> | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<Map srs="+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over" background-color="#00000000" buffer-size="128"> | |||
<Style name="geodetdom" filter-mode="first" > | |||
<Rule> | |||
<PointSymbolizer file="icon.png" allow-overlap="true"/> | |||
</Rule> | |||
</Style> | |||
<Layer name="geodetdom" srs="+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs"> | |||
<StyleName>geodetdom</StyleName> | |||
<Datasource> | |||
<Parameter name="type">postgis</Parameter> | |||
<Parameter name="host">gis-lab.info</Parameter> | |||
<Parameter name="dbname">geodetdom</Parameter> | |||
<Parameter name="table">data</Parameter> | |||
<Parameter name="geometry_field">geometry</Parameter> | |||
<Parameter name="user">guest</Parameter> | |||
<Parameter name="password">guest</Parameter> | |||
</Datasource> | |||
</Layer> | |||
</Map> | |||
</syntaxhighlight> | |||
В эту же директорию поместим файл icon.png, которым будут отрисовываться | |||
наши данные (любая растровая иконка): | |||
[[Файл:Tilecache-embedded-05.png|32px|thumb|center]] | |||
'''Функция обработки входящих запросов''' | |||
Представим код функции и в комментариях опишем, что в ней происходит: | |||
<syntaxhighlight lang="python"> | |||
def view_tilecache(request): | |||
global _tilecache_service | |||
# Настройки слоя Tilecache | |||
layer_args = dict( | |||
srs='EPSG:3857', | |||
spherical_mercator='yes', | |||
tms_type='google', | |||
debug='no' | |||
) | |||
# Создаем сервис (объект класса TileCache.Service.Service), | |||
# указывая объект кэша, конфигурационный файл Mapnik-а и словарь | |||
# слоев, в котором в качестве ключа используется имя слоя, которое | |||
# будет передаваться в URL (согласно спецификации TMS) | |||
if not _tilecache_service: | |||
# Определяем путь до конфигурационного файла Mapnik | |||
mapfile = os.path.join(os.path.split(__file__)[0], 'static', 'mapnik.xml') | |||
# Здесь мы могли создать объект какого-нибудь стандартного | |||
# кэша TileCache, например TileCache.Caches.Disk | |||
cache = PostgresCache() | |||
# Сохраняем объект сервиса в глобальной переменной | |||
_tilecache_service = Service( | |||
cache, | |||
# Словарь доступных слоев | |||
dict( | |||
geodetdom=Mapnik('domiki', mapfile, cache=cache, **layer_args) | |||
) | |||
) | |||
# Так как шаблон используемого URL имеет вид '/tilecache/{path:.*}', | |||
# то в переменную path извлекается та часть URL, которая следует | |||
# за '/tilecache/'. | |||
path = request.matchdict['path'] | |||
# Вызываем метод dispatchRequest сервиса _tilecache_service, | |||
# который в по виду URL определяет какой запрос пришел | |||
# от пользователя (TileCache поддерживает не только TMS, но и | |||
# WMS-C и WorldWind, а также может возвращать документы с метаданными) и | |||
# формирует ответ соответствующего типа | |||
format, body = _tilecache_service.dispatchRequest( | |||
request.params, | |||
path, | |||
request.method | |||
) | |||
# Выставляем значение заголовка Expires для того, чтобы | |||
# браузер смог кэшировать тайлы на стороне клиента | |||
expires = datetime.datetime.utcnow() + datetime.timedelta(seconds=3600) | |||
# Возвращаем результат клиенту | |||
return Response(body, content_type=format, expires=expires) | |||
</syntaxhighlight> | |||
'''Запуск сервиса''' | |||
Запускаем наш сервис: | |||
<syntaxhighlight lang="bash"> | |||
pserve ~/cache/cache/development.ini | |||
</syntaxhighlight> | |||
По умолчанию сервис будет запущен на порту с номером 6543. | |||
== Результаты == | |||
Открываем браузер и в адресной строке вводим URL следующего вида ( | |||
подразумевается, что клиент и сервер физически находятся на одной машине, | |||
если у вас не так, то вместо localhost введите имя хоста, на котором | |||
запущен сервис): | |||
<syntaxhighlight lang="bash"> | |||
http://localhost:6543/tilecache/1.0.0/geodetdom/0/0/0.png | |||
</syntaxhighlight> | |||
В ответ вы должны получить следующее изображение: | |||
[[Файл:Tilecache-embedded-01.png|256px|thumb|center|<center>Пример запрошенного тайла</center>]] | |||
Кроме того, созданный тайл будет помещен в базу данных: | |||
[[Файл:Tilecache-embedded-02.png|514px|thumb|center|<center>Пример закэшированного тайла</center>]] | |||
Подключим созданный нами TMS-слой в OpenLayers: | |||
<syntaxhighlight lang="html4strict"> | |||
<html xmlns="http://www.w3.org/1999/xhtml"> | |||
<head> | |||
<title>Geodetdom</title> | |||
<script src="http://openlayers.org/dev/OpenLayers.js"></script> | |||
<script type="text/javascript"> | |||
function init(){ | |||
options = { | |||
div: "map", | |||
zoom: 1, | |||
center: [0,0], | |||
layers: [ | |||
new OpenLayers.Layer.OSM() | |||
] | |||
}; | |||
map = new OpenLayers.Map(options); | |||
var domiki = new OpenLayers.Layer.XYZ('domiki', | |||
['http://localhost:6543/tilecache/1.0.0/geodetdom/${z}/${x}/${y}.png'], | |||
{ | |||
sphericalMercator: true, | |||
isBaseLayer: false | |||
}); | |||
map.addLayer(domiki); | |||
} | |||
</script> | |||
</head> | |||
<body onload="init()"> | |||
<div id="map" style="width:640px; height:480px;"></div> | |||
</body> | |||
</html> | |||
</syntaxhighlight> | |||
Выглядеть это будет следующим образом: | |||
[[Файл:Tilecache-embedded-03.png|634px|thumb|center|<center>Подключение созданного TMS-слоя в OpenLayers</center>]] | |||
И кэш в СУБД значительно пополнится: | |||
[[Файл:Tilecache-embedded-04.png|510px|thumb|center|<center>Заполненный кэш</center>]] | |||
== Заключение == | |||
Представленный вариант интеграции возможностей TileCache был | |||
использован компанией NextGIS при разработке картографической подсистемы | |||
[http://nextgis.ru/blog/msx-lanit/ портала] системы государственного информационного обеспечения в сфере сельского | |||
хозяйства (СГИО СХ). |
Текущая версия от 08:46, 24 июля 2013
по адресу http://gis-lab.info/qa/tilecache-embedded.html
Рассмотрена процедура встраивания TileCache в собственное приложение, а также приведен пример создания кэша на базе PostgreSQL.
Введение
Предположим, что вы разрабатываете клиент-серверную Веб-ГИС. Вам требуется отображать на клиенте некоторую растровую подложку, которая создается на базе данных хранилища (БД), к которому имеет доступ серверная компонента. Стандартным решением подобной задачи является создание на сервере Tile Map Service (TMS, спецификация) и подключение его на клиенте.
Виды TMS-сервисов
TMS-сервис может функционировать в одном из двух режимов: статическом и динамическом. Статический TMS - это предварительно созданный набор тайлов, он удобен если данные, на основе которых будет создаваться TMS-слой, одинаковы для всех установок приложения (например, данные о государственных границах) и распространяются вместе с приложением. Разумеется, это может привести к недопустимому росту размера приложения и поэтому статический TMS может также предоставлять пользователю возможность генерирования тайлов "на месте", чтобы не распространять их с приложением.
Недостаток этого варианта заключается в том, что пользователю, для того чтобы начать работать с приложением, потребуется время на создание кэша (может занимать от нескольких часов до нескольких дней). Динамической TMS решает эту проблему и генерирует тайлы по запросу.
Почему могут не подойти готовые решения
Существующее ПО для создания динамических TMS-сервисов (TileCache, MapProxy) является самостоятельным программным обеспечением и изначально не предназначено для использования в качестве встраиваемых решений. Использовать MapProxy или TileCache по их прямому назначению может быть невозможно, если:
- Пользователь по той или иной причине не может отдельно установить и настроить тайловый сервер;
- Доступ к тайлам должен предоставляться не всем пользователям, а только тем, которые имеют на это права согласно подсистеме разделения прав, используемой в приложении (если таковая имеется).
Решение
Поэтому решение данной задачи сводится к написанию собственного TMS-сервиса и интеграции его в приложение. Пример решения подобной задачи был рассмотрен в статье Основы работы динамических TMS-сервисов, но основным недостатком получившегося там сервиса является то, что он не поддерживает процедуру кэширования, что очень важно при разработке реального приложения.
В рамках данной статьи рассмотрим создание кэширующего TMS-сервиса на базе классов, предоставляемых TileCache. TileCache был выбран в качестве базовой системы в виду того, что он имеет довольно простую и понятную архитектуру в отличие от того же MapProxy, хотя по функционалу в целом значительно уступает последнему. В качестве рендерера будем использовать Mapnik.
В качестве языка программирования будем использовать Python, операционная система - Debian GNU/Linux 7.0.
Создание каркаса приложения
Во избежание написания большого количества служебного кода в качестве каркаса нашего приложения будем использовать Веб-фреймворк Pyramid.
Создание каталога будущего проекта
mkdir ~/cache
cd ~/cache
Создание виртуального окружения
virtualenv --no-site-packages env
Установка Pyramid
source env/bin/activate
pip install pyramid
Генерирование структуры проекта
pcreate -s alchemy cache
В файл ~/cache/cache/setup.py в массив requires к имеющимся пакетам добавляем имена пакетов, которые будут использоваться в нашем проекте: psycopg2 и TileCache.
Установка проекта в режиме разработки
cd cache
python setup.py develop
Установка Mapnik
Идейно верное решение - это указать Mapnik как зависимость в файле setup.py для его автоматической установки, но на практике установка Mapnik в виртуальное окружение представляет собой довольно сложную задачу, поэтому для перехода к следующему шагу установите Mapnik в систему, после чего сделайте симлинк на директорию с Python-пакетами виртуального окружения. Для того, чтобы узнать, куда был установлен Mapnik, запустите системный Python и выполните следующие команды:
import mapnik
mapnik.__file__
'/usr/lib/python2.7/dist-packages/mapnik/__init__.py'
Как можно видеть, в нашем случае Mapnik был установлен в каталог /usr/lib/python2.7/dist-packages/mapnik. Находясь в активном виртуальном окружении, создаем симлинк:
ln -s /usr/lib/python2.7/dist-packages/mapnik $VIRTUAL_ENV/lib/python2.7/site-packages/mapnik
Теперь, если запустить Python в виртуальном окружении и дать команду:
import mapnik
то не должно появляться никаких сообщений об ошибках, что свидетельствует о том, что системный Mapnik виден из виртуального окружения.
Хранение кэшированных данных в PostgreSQL
Среди списка поддерживаемых кэшей в Tilecache отсутствуют те или иные СУБД. Конечно, в качестве учебного примера мы могли бы остановиться на использовании какого-нибудь стандартного кэша, например, файлового, но мы немного усложним задачу, реализовав собственный класс, отвечающий за хранение кэшированных данных в СУБД PostgreSQL. Будем считать, что PostgreSQL установлен на той же машине, что и разрабатываемое приложение.
Создание базы данных
Создадим на уровне БД отдельного пользователя, назовём его cacheuser (пароль secret):
sudo su postgres -c "createuser -P -e cacheuser"
Введите пароль для новой роли:
Повторите его:
Должна ли новая роль иметь полномочия суперпользователя? (y - да/n - нет) n
Новая роль должна иметь право создавать базы данных? (y - да/n - нет) n
Новая роль должна иметь право создавать другие роли? (y - да/n - нет) n
CREATE ROLE cacheuser PASSWORD 'md57ea40027c2db9dc1b1b85ad7bf0f5314' NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN;
Мы создали пользователя, который не обладает правами суперпользователя, не может создавать базы данных и другие роли. От имени пользователя postgres создадим базу данных tilecache и сделаем пользователя cacheuser её владельцем:
sudo su postgres -c "createdb -O cacheuser --encoding=UTF8 tilecache"
Подключение базы данных в приложение
Подключаем созданную БД в наше приложение, для этого в файле ~/cache/cache/development.ini редактируем строку sqlalchemy.url:
sqlalchemy.url = postgresql+psycopg2://cacheuser:secret@localhost/tilecache
Описание модели кэша
Для взаимодействия нашего приложения с базой данных будем использовать ORM SQLAlchemy. Для описания структуры таблицы базы данных, в которой будут храниться кэшированные данные, воспользуемся декларативным синтаксисом SQLAlchemy. Для этого открываем файл ~/cache/cache/cache/models.py и помещаем в него следующее содержимое:
# -*- coding: utf-8 -*-
from sqlalchemy import Column
from sqlalchemy import Integer, Unicode, LargeBinary
from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from zope.sqlalchemy import ZopeTransactionExtension
DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
Base = declarative_base()
# Хранилище тайлов TMS-слоя
class TileCache(Base):
__tablename__ = 'tilecache'
layer = Column(Unicode(50), primary_key=True)
z = Column(Integer, primary_key=True)
x = Column(Integer, primary_key=True)
y = Column(Integer, primary_key=True)
data = Column(LargeBinary)
Из представленного кода видно, что кэшированные данные будут храниться в таблице tilecache, содержащей 5 полей: layer - поле, содержащее имя слой, тайл которого представлен в записи, x, y, z - координаты тайла, data - собственно сам тайл. Если в вашей базе данных установлен PostGIS, то имеет смысл добавить в эту таблицу дополнительное поле геометрии, которое будет содержать в себе значение охвата тайла. Это поле в дальнейшем очень удобно использовать, например, для очистки фрагмента кэша по какому-либо пространственному условию.
Инициализация базы данных
По описанному классу создадим таблицу в базе данных. Для этого откройте файл ~/cache/cache/cache/scripts/initializedb.py и приведите его в соответствие со следующим фрагментом:
import os
import sys
import transaction
from sqlalchemy import engine_from_config
from pyramid.paster import (
get_appsettings,
setup_logging,
)
from ..models import (
DBSession,
Base
)
def usage(argv):
cmd = os.path.basename(argv[0])
print('usage: %s <config_uri>\n'
'(example: "%s development.ini")' % (cmd, cmd))
sys.exit(1)
def main(argv=sys.argv):
if len(argv) != 2:
usage(argv)
config_uri = argv[1]
setup_logging(config_uri)
settings = get_appsettings(config_uri)
engine = engine_from_config(settings, 'sqlalchemy.')
DBSession.configure(bind=engine)
Base.metadata.create_all(engine)
После этого выполните команду:
initialize_cache_db ~/cache/cache/development.ini
Должно появиться следующее сообщение, свидетельствующее о том, что таблица успешно создана:
2013-07-17 12:05:08,945 INFO [sqlalchemy.engine.base.Engine][MainThread] select version()
2013-07-17 12:05:08,945 INFO [sqlalchemy.engine.base.Engine][MainThread] {}
2013-07-17 12:05:08,961 INFO [sqlalchemy.engine.base.Engine][MainThread] select current_schema()
2013-07-17 12:05:08,962 INFO [sqlalchemy.engine.base.Engine][MainThread] {}
2013-07-17 12:05:08,980 INFO [sqlalchemy.engine.base.Engine][MainThread] select relname from pg_class c join pg_namespace n on n.oid=c.relnamespace where n.nspname=current_schema() and relname=%(name)s
2013-07-17 12:05:08,980 INFO [sqlalchemy.engine.base.Engine][MainThread] {'name': u'tilecache'}
2013-07-17 12:05:08,993 INFO [sqlalchemy.engine.base.Engine][MainThread]
CREATE TABLE tilecache (
layer VARCHAR(50) NOT NULL,
z SERIAL NOT NULL,
x INTEGER NOT NULL,
y INTEGER NOT NULL,
data BYTEA,
PRIMARY KEY (layer, z, x, y)
)
2013-07-17 12:05:08,998 INFO [sqlalchemy.engine.base.Engine][MainThread] {}
2013-07-17 12:05:09,286 INFO [sqlalchemy.engine.base.Engine][MainThread] COMMIT
Создание кэширующего TMS-сервиса
Итак, все подготовительные работы закончены, переходим непосредственно к созданию сервиса. Откройте файл ~/cache/cache/cache/__init__.py и приведите его в соответствие со следующим фрагментом:
from pyramid.config import Configurator
from sqlalchemy import engine_from_config
from cache.models import DBSession
from cache.models import Base
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
engine = engine_from_config(settings, 'sqlalchemy.')
DBSession.configure(bind=engine)
Base.metadata.bind = engine
config = Configurator(settings=settings)
config.add_route('tilecache', '/tilecache/{path:.*}')
config.add_view('cache.tilecache.view_tilecache', route_name='tilecache')
return config.make_wsgi_app()
Данная функция обеспечивает следующую функциональность: любой запрос, имеющий URL вида /tilecache/* (где * - произвольная последовательность символов), будет обработан функцией cache.tilecache.view_tilecache.
Переходим к написанию этой функции. Создайте файл ~/cache/cache/cache/tilecache.py:
# -*- coding: utf-8 -*-
import datetime
import os
from TileCache.Cache import Cache
from TileCache.Service import Service
from TileCache.Layers.Mapnik import Mapnik
from pyramid.response import Response
from cache.models import DBSession
from cache.models import TileCache as TileCacheModel
_tilecache_service = None
# Весь дальнейший код будет помещен сюда
Описание класса кэша PostgreSQL
В TileCache в роли кэша может выступать любой объект класса, имеющий 2 обязательных метода: get и set. Первый отвечает за извлечение данных из кэша, второй - за помещение их туда (в случае если запрошенный клиентом тайл отстутствует в кэше). Здесь же, в файле tilecache.py, описываем соответствующий класс, унаследовавшись от базового класса TileCache.Cache.Cache:
class PostgresCache(Cache):
def __init__(self, **kwargs):
Cache.__init__(self, **kwargs)
def get(self, tile):
dbsession = DBSession()
obj = dbsession.query(TileCacheModel).\
filter_by(
layer=tile.layer.name,
z=tile.z,
x=tile.x,
y=tile.y
).first()
if obj:
return obj.data
else:
return None
def set(self, tile, data):
dbsession = DBSession()
obj = TileCacheModel(
layer=tile.layer.name,
z=tile.z,
x=tile.x,
y=tile.y,
data=data
)
dbsession.add(obj)
dbsession.flush()
return data
Подготовка конфигурационного XML-файла Mapnik
При запросе клиентом тайла в случае если его нет в кэше, его нужно создать (отрендерить). Как уже было сказано выше для этой цели мы будем использовать Mapnik.
В качестве источника данных воспользуемся результатами проекта, в рамках которого был создан открытый точечный слой геоданных по детским учреждениям (детским домам). Этот слой также доступен в БД PostGIS.
Создадим файл ~/cache/cache/cache/static/mapnik.xml следующего содержания:
<?xml version="1.0" encoding="utf-8"?>
<Map srs="+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over" background-color="#00000000" buffer-size="128">
<Style name="geodetdom" filter-mode="first" >
<Rule>
<PointSymbolizer file="icon.png" allow-overlap="true"/>
</Rule>
</Style>
<Layer name="geodetdom" srs="+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs">
<StyleName>geodetdom</StyleName>
<Datasource>
<Parameter name="type">postgis</Parameter>
<Parameter name="host">gis-lab.info</Parameter>
<Parameter name="dbname">geodetdom</Parameter>
<Parameter name="table">data</Parameter>
<Parameter name="geometry_field">geometry</Parameter>
<Parameter name="user">guest</Parameter>
<Parameter name="password">guest</Parameter>
</Datasource>
</Layer>
</Map>
В эту же директорию поместим файл icon.png, которым будут отрисовываться наши данные (любая растровая иконка):
Функция обработки входящих запросов
Представим код функции и в комментариях опишем, что в ней происходит:
def view_tilecache(request):
global _tilecache_service
# Настройки слоя Tilecache
layer_args = dict(
srs='EPSG:3857',
spherical_mercator='yes',
tms_type='google',
debug='no'
)
# Создаем сервис (объект класса TileCache.Service.Service),
# указывая объект кэша, конфигурационный файл Mapnik-а и словарь
# слоев, в котором в качестве ключа используется имя слоя, которое
# будет передаваться в URL (согласно спецификации TMS)
if not _tilecache_service:
# Определяем путь до конфигурационного файла Mapnik
mapfile = os.path.join(os.path.split(__file__)[0], 'static', 'mapnik.xml')
# Здесь мы могли создать объект какого-нибудь стандартного
# кэша TileCache, например TileCache.Caches.Disk
cache = PostgresCache()
# Сохраняем объект сервиса в глобальной переменной
_tilecache_service = Service(
cache,
# Словарь доступных слоев
dict(
geodetdom=Mapnik('domiki', mapfile, cache=cache, **layer_args)
)
)
# Так как шаблон используемого URL имеет вид '/tilecache/{path:.*}',
# то в переменную path извлекается та часть URL, которая следует
# за '/tilecache/'.
path = request.matchdict['path']
# Вызываем метод dispatchRequest сервиса _tilecache_service,
# который в по виду URL определяет какой запрос пришел
# от пользователя (TileCache поддерживает не только TMS, но и
# WMS-C и WorldWind, а также может возвращать документы с метаданными) и
# формирует ответ соответствующего типа
format, body = _tilecache_service.dispatchRequest(
request.params,
path,
request.method
)
# Выставляем значение заголовка Expires для того, чтобы
# браузер смог кэшировать тайлы на стороне клиента
expires = datetime.datetime.utcnow() + datetime.timedelta(seconds=3600)
# Возвращаем результат клиенту
return Response(body, content_type=format, expires=expires)
Запуск сервиса
Запускаем наш сервис:
pserve ~/cache/cache/development.ini
По умолчанию сервис будет запущен на порту с номером 6543.
Результаты
Открываем браузер и в адресной строке вводим URL следующего вида ( подразумевается, что клиент и сервер физически находятся на одной машине, если у вас не так, то вместо localhost введите имя хоста, на котором запущен сервис):
http://localhost:6543/tilecache/1.0.0/geodetdom/0/0/0.png
В ответ вы должны получить следующее изображение:
Кроме того, созданный тайл будет помещен в базу данных:
Подключим созданный нами TMS-слой в OpenLayers:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Geodetdom</title>
<script src="http://openlayers.org/dev/OpenLayers.js"></script>
<script type="text/javascript">
function init(){
options = {
div: "map",
zoom: 1,
center: [0,0],
layers: [
new OpenLayers.Layer.OSM()
]
};
map = new OpenLayers.Map(options);
var domiki = new OpenLayers.Layer.XYZ('domiki',
['http://localhost:6543/tilecache/1.0.0/geodetdom/${z}/${x}/${y}.png'],
{
sphericalMercator: true,
isBaseLayer: false
});
map.addLayer(domiki);
}
</script>
</head>
<body onload="init()">
<div id="map" style="width:640px; height:480px;"></div>
</body>
</html>
Выглядеть это будет следующим образом:
И кэш в СУБД значительно пополнится:
Заключение
Представленный вариант интеграции возможностей TileCache был использован компанией NextGIS при разработке картографической подсистемы портала системы государственного информационного обеспечения в сфере сельского хозяйства (СГИО СХ).