From 8d336dcf7d78745a3ecca08be79fa06fbf0c069a Mon Sep 17 00:00:00 2001 From: Jean-Francois Doyon Date: Sat, 8 Apr 2006 06:18:50 +0000 Subject: [PATCH] Adding the initial implementation of an OGC server in Python for Mapnik. Although it works, it needs polishing and documentation, which will follow shortly. --- bindings/python/mapnik/ogcserver/__init__.py | 20 + bindings/python/mapnik/ogcserver/cgiserver.py | 87 ++++ bindings/python/mapnik/ogcserver/common.py | 161 +++++++ .../python/mapnik/ogcserver/exceptions.py | 26 ++ bindings/python/mapnik/ogcserver/wms.py | 411 ++++++++++++++++++ 5 files changed, 705 insertions(+) create mode 100644 bindings/python/mapnik/ogcserver/__init__.py create mode 100644 bindings/python/mapnik/ogcserver/cgiserver.py create mode 100644 bindings/python/mapnik/ogcserver/common.py create mode 100644 bindings/python/mapnik/ogcserver/exceptions.py create mode 100644 bindings/python/mapnik/ogcserver/wms.py diff --git a/bindings/python/mapnik/ogcserver/__init__.py b/bindings/python/mapnik/ogcserver/__init__.py new file mode 100644 index 000000000..eb1ae14a7 --- /dev/null +++ b/bindings/python/mapnik/ogcserver/__init__.py @@ -0,0 +1,20 @@ +# +# This file is part of Mapnik (c++ mapping toolkit) +# +# Copyright (C) 2006 Jean-Francois Doyon +# +# Mapnik is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +# $Id$ \ No newline at end of file diff --git a/bindings/python/mapnik/ogcserver/cgiserver.py b/bindings/python/mapnik/ogcserver/cgiserver.py new file mode 100644 index 000000000..957510fff --- /dev/null +++ b/bindings/python/mapnik/ogcserver/cgiserver.py @@ -0,0 +1,87 @@ +# +# This file is part of Mapnik (c++ mapping toolkit) +# +# Copyright (C) 2006 Jean-Francois Doyon +# +# Mapnik is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +# $Id$ + +import jon.cgi as cgi +from os import environ, access +from exceptions import OGCException +import sys +from copy import deepcopy +from tempfile import gettempdir + +environ['PYTHON_EGG_CACHE'] = gettempdir() +from lxml import etree as ElementTree + +class Handler(cgi.DebugHandler): + + ogcexcetree = ElementTree.fromstring(""" + + + + """) + + def __init__(self): + self.requesthandlers = {} + + def process(self, req): + reqparams = {} + for key, value in req.params.items(): + reqparams[key.lower()] = value + onlineresource = 'http://%s:%s%s' % (req.environ['SERVER_NAME'], req.environ['SERVER_PORT'], req.environ['SCRIPT_NAME']) + try: + if not reqparams.has_key('request'): + raise OGCException('Missing request parameter.') + request = reqparams['request'].lower() + if request == 'getcapabilities' and not reqparams.has_key('service'): + raise OGCException('Missing service parameter.') + if request in ['getmap', 'getfeatureinfo']: + reqparams['service'] = 'wms' + service = reqparams['service'].lower() + srkey = (service, request) + if self.requesthandlers.has_key(srkey): + requesthandler = self.requesthandlers[srkey] + else: + try: + mapnikmodule = __import__('mapnik.ogcserver.' + service) + except ImportError: + raise OGCException('Service "%s" not supported.' % service) + ServiceFactory = getattr(mapnikmodule.ogcserver, service).ServiceFactory + servicehandler = ServiceFactory(self.configpath, self.factory, onlineresource, reqparams.get('version', None)) + if request not in servicehandler.SERVICE_PARAMS.keys(): + raise OGCException('Operation "%s" not supported.' % request, 'OperationNotSupported') + ogcparams = servicehandler.processParameters(request, reqparams) + try: + requesthandler = getattr(servicehandler, request) + except: + raise OGCException('Operation "%s" not supported.' % request, 'OperationNotSupported') + else: + self.requesthandlers[srkey] = requesthandler + response = requesthandler(ogcparams) + except OGCException: + req.set_header('Content-Type', 'text/xml') + ogcexcetree = deepcopy(self.ogcexcetree) + e = ogcexcetree.find('ServiceException') + e.text = sys.exc_value.args[0] + if len(sys.exc_value.args) == 2: + e.set('code', sys.exc_value.args[1]) + req.write(ElementTree.tostring(ogcexcetree)) + else: + req.set_header('Content-Type', response.content_type) + req.write(response.content) \ No newline at end of file diff --git a/bindings/python/mapnik/ogcserver/common.py b/bindings/python/mapnik/ogcserver/common.py new file mode 100644 index 000000000..bb871743f --- /dev/null +++ b/bindings/python/mapnik/ogcserver/common.py @@ -0,0 +1,161 @@ +# +# This file is part of Mapnik (c++ mapping toolkit) +# +# Copyright (C) 2006 Jean-Francois Doyon +# +# Mapnik is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +# $Id$ + +from exceptions import OGCException, ServerConfigurationError +from mapnik import Color +import re + +PIL_TYPE_MAPPING = {'image/jpeg': 'JPEG', 'image/png': 'PNG', 'image/gif': 'GIF'} + +class ParameterDefinition: + + def __init__(self, mandatory, default=None, allowedvalues=None, fallback=False, match=None, cast=None): + """ An OGC request parameter definition. Used to describe a + parameter's characteristics. + + @param mandatory: Is this parameter required by the request? + @type mandatory: Boolean. + + @param default: Default value to use if one is not provided + and the parameter is optional. + @type default: None or any valid value. + + @param allowedvalues: A list of allowed values for the parameter. + If a value is provided that is not in this + list, an error is raised. + @type allowedvalues: A python tuple of values. + + @param fallback: Whether the value of the parameter should fall + back to the default should an illegal value be + provided. + @type fallback: Boolean. + + @return: A L{ParameterDefinition} instance. + """ + if match and cast: + raise ServerConfigurationError("'cast' and 'match' are mutually exclusive.") + if not match and not cast: + raise ServerConfigurationError("One of 'cast' or 'match' MUST be used.") + if match: + try: + self.pattern = re.compile(match) + except: + raise ServerConfigurationError("Invalid regular expression.") + else: + self.pattern = None + if cast and not callable(cast): + raise ServerConfigurationError('Cast parameter definition must be callable.') + self.cast = cast + if mandatory not in [True, False]: + raise ServerConfigurationError("Bad value for 'mandatory' parameter, must be True or False.") + self.mandatory = mandatory + self.default = default + if allowedvalues and type(allowedvalues) != type(()): + raise ServerConfigurationError("Bad value for 'allowedvalues' parameter, must be a tuple.") + self.allowedvalues = allowedvalues + if fallback not in [True, False]: + raise ServerConfigurationError("Bad value for 'fallback' parameter, must be True or False.") + self.fallback = fallback + +class BaseServiceHandler: + + def processParameters(self, requestname, params): + finalparams = {} + for paramname, paramdef in self.SERVICE_PARAMS[requestname].items(): + if paramname not in params.keys() and paramdef.mandatory: + raise OGCException("Mandatory parameter '%s' missing from request." % paramname) + elif paramname in params.keys(): + if paramdef.pattern: + if not paramdef.pattern.match(params[paramname]): + raise OGCException("Parameter '%s' has an illegal value." % paramname) + elif paramdef.cast: + try: + params[paramname] = paramdef.cast(params[paramname]) + except: + raise OGCException("Parameter '%s' has an illegal value." % paramname) + if paramdef.allowedvalues and params[paramname] not in paramdef.allowedvalues: + if not paramdef.fallback: + raise OGCException("Parameter '%s' has an illegal value." % paramname) + else: + finalparams[paramname] = paramdef.default + else: + finalparams[paramname] = params[paramname] + elif not paramdef.mandatory and paramdef.default: + finalparams[paramname] = paramdef.default + return finalparams + +class Response: + + def __init__(self, content_type, content): + self.content_type = content_type + self.content = content + +class Version: + + def __init__(self, version): + version = version.split('.') + if len(version) != 3: + raise OGCException('Badly formatted version number.') + try: + version = map(int, version) + except: + raise OGCException('Badly formatted version number.') + self.version = version + + def __repr__(self): + return '%s.%s.%s' % (self.version[0], self.version[1], self.version[2]) + + def __cmp__(self, other): + if isinstance(other, str): + other = Version(other) + if not isinstance(other, Version): + raise TypeError('Version instances can only be compared to each other, or version strings.') + if self.version[0] < other.version[0]: + return -1 + elif self.version[0] > other.version[0]: + return 1 + else: + if self.version[1] < other.version[1]: + return -1 + elif self.version[1] > other.version[1]: + return 1 + else: + if self.version[2] < other.version[2]: + return -1 + elif self.version[2] > other.version[2]: + return 1 + else: + return 0 + +class ListFactory: + + def __init__(self, cast): + self.cast = cast + + def __call__(self, string): + seq = string.split(',') + return map(self.cast, seq) + +def ColorFactory(colorstring): + if re.match('^0x[a-fA-F0-9]{6}$', colorstring): + return Color(eval('0x' + colorstring[2:4]), eval('0x' + colorstring[4:6]), eval('0x' + colorstring[6:8])) + else: + raise OGCException("Invalid color value.") \ No newline at end of file diff --git a/bindings/python/mapnik/ogcserver/exceptions.py b/bindings/python/mapnik/ogcserver/exceptions.py new file mode 100644 index 000000000..215de33fd --- /dev/null +++ b/bindings/python/mapnik/ogcserver/exceptions.py @@ -0,0 +1,26 @@ +# +# This file is part of Mapnik (c++ mapping toolkit) +# +# Copyright (C) 2006 Jean-Francois Doyon +# +# Mapnik is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +# $Id$ + +class OGCException(Exception): + pass + +class ServerConfigurationError(Exception): + pass \ No newline at end of file diff --git a/bindings/python/mapnik/ogcserver/wms.py b/bindings/python/mapnik/ogcserver/wms.py new file mode 100644 index 000000000..a3876d380 --- /dev/null +++ b/bindings/python/mapnik/ogcserver/wms.py @@ -0,0 +1,411 @@ +# +# This file is part of Mapnik (c++ mapping toolkit) +# +# Copyright (C) 2006 Jean-Francois Doyon +# +# Mapnik is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +# $Id$ + +from common import ParameterDefinition, BaseServiceHandler, Response, \ + PIL_TYPE_MAPPING, Version, ListFactory, ColorFactory +from exceptions import OGCException, ServerConfigurationError +from ConfigParser import SafeConfigParser +from mapnik import Map, Color, Envelope, render, rawdata, Image, Projection, \ + DEGREES +from PIL.Image import fromstring +from StringIO import StringIO +from lxml import etree as ElementTree + +class ServiceHandler111(BaseServiceHandler): + + SERVICE_PARAMS = { + 'getcapabilities': { + 'updatesequence': ParameterDefinition(False, cast=str) + }, + 'getmap': { + 'version': ParameterDefinition(True, allowedvalues=(Version('1.1.1'),), cast=Version), + 'layers': ParameterDefinition(True, cast=ListFactory(str)), + 'styles': ParameterDefinition(True, cast=ListFactory(str)), + 'srs': ParameterDefinition(True, match='^EPSG:\d+$'), + 'bbox': ParameterDefinition(True, cast=ListFactory(float)), + 'width': ParameterDefinition(True, cast=int), + 'height': ParameterDefinition(True, cast=int), + 'format': ParameterDefinition(True, allowedvalues=('image/png','image/jpeg','image/gif'), cast=str.lower), + 'transparent': ParameterDefinition(False, 'FALSE', allowedvalues=('true','false'), cast=str.lower), + 'bgcolor': ParameterDefinition(False, ColorFactory('0xFFFFFF'), cast=ColorFactory), + 'exceptions': ParameterDefinition(False, 'application/vnd.ogc.se_xml', ('application/vnd.ogc.se_xml',), cast=str) + } + } + + CONF_SERVICE = [ + ['title', 'Title', str], + ['abstract', 'Abstract', str], + ['onlineresource', 'OnlineResource', str], + ['fees', 'Fees', str], + ['accessconstraints', 'AccessConstraints', str], + ] + + capabilitiesxmltemplate = """ + + + + WMS + + + + + application/vnd.ogc.wms_xml + + + + + + + + + + + + + image/png + image/jpeg + image/gif + + + + + + + + + + + + + + application/vnd.ogc.se_xml + + + A Mapnik WMS Server + A Mapnik WMS Server + + + + + """ + + def __init__(self, configpath, factory, opsonlineresource): + self.factory = factory + self.conf = SafeConfigParser() + self.conf.readfp(open(configpath)) + if self.conf.has_option('service', 'epsg'): + self.epsgcode = self.conf.get('service', 'epsg') + self.proj = Projection(['init=epsg:' + self.epsgcode]) + else: + raise "Missing EPSG code" + if not opsonlineresource.endswith('?'): opsonlineresource += '?' + + capetree = ElementTree.fromstring(self.capabilitiesxmltemplate) + + elements = capetree.findall('Capability//OnlineResource') + for element in elements: + element.set('{http://www.w3.org/1999/xlink}href', opsonlineresource) + + if len(self.conf.items('service')) > 0: + servicee = capetree.find('Service') + for item in self.CONF_SERVICE: + if self.conf.has_option('service', item[0]): + value = self.conf.get('service', item[0]) + try: + item[2](value) + except: + raise ServerConfigurationError('Configuration parameter [%s]->%s has an invalid value: %s.' % ('service', item[0], value)) + if item[0] == 'onlineresource': + element = ElementTree.Element('%s' % item[1]) + servicee.append(element) + element.set('{http://www.w3.org/1999/xlink}href', value) + element.set('{http://www.w3.org/1999/xlink}type', 'simple') + else: + element = ElementTree.Element('%s' % item[1]) + element.text = value + servicee.append(element) + + rootlayerelem = capetree.find('Capability/Layer') + + rootlayersrs = rootlayerelem.find('SRS') + rootlayersrs.text = 'EPSG:%s' % self.epsgcode + + dict = self.factory() + + for layer in dict['layers']: + layername = ElementTree.Element('Name') + layername.text = layer.name() + layertitle = ElementTree.Element('Title') + layertitle.text = layer.name() + env = layer.envelope() + llp = self.proj.Inverse(env.minx, env.miny) + urp = self.proj.Inverse(env.maxx, env.maxy) + latlonbb = ElementTree.Element('LatLonBoundingBox') + latlonbb.set('minx', str(llp[0])) + latlonbb.set('miny', str(llp[1])) + latlonbb.set('maxx', str(urp[0])) + latlonbb.set('maxy', str(urp[1])) + layerbbox = ElementTree.Element('BoundingBox') + layerbbox.set('SRS', 'EPSG:%s' % self.epsgcode) + layerbbox.set('minx', str(env.minx)) + layerbbox.set('miny', str(env.miny)) + layerbbox.set('maxx', str(env.maxx)) + layerbbox.set('maxy', str(env.maxy)) + layere = ElementTree.Element('Layer') + layere.append(layername) + layere.append(layertitle) + layere.append(latlonbb) + layere.append(layerbbox) + rootlayerelem.append(layere) + + self.capabilities = '\n' + ElementTree.tostring(capetree) + + def getcapabilities(self, params): + response = Response('application/vnd.ogc.wms_xml', self.capabilities) + return response + + def getmap(self, params): + m = Map(params['width'], params['height']) + if params['transparent'].lower() == 'false': + m.background = params['bgcolor'] + mo = self.factory() + for layername in params['layers']: + for layer in mo['layers']: + if layer.name() == layername: + for stylename in layer.styles: + if stylename in mo['styles'].keys(): + m.append_style(stylename, mo['styles'][stylename]) + m.layers.append(layer) + m.zoom_to_box(Envelope(params['bbox'][0], params['bbox'][1], params['bbox'][2], params['bbox'][3])) + im = Image(params['width'], params['height']) + render(m, im) + im2 = fromstring('RGBA', (params['width'], params['height']), rawdata(im)) + fh = StringIO() + im2.save(fh, PIL_TYPE_MAPPING[params['format']]) + fh.seek(0) + response = Response(params['format'], fh.read()) + return response + +class ServiceHandler130(BaseServiceHandler): + + SERVICE_PARAMS = { + 'getcapabilities': { + 'format': ParameterDefinition(False, 'text/xml', ('text/xml',), True, cast=str), + }, + 'getmap': { + 'version': ParameterDefinition(True, allowedvalues=('1.3.0',), cast=Version), + 'layers': ParameterDefinition(True, cast=ListFactory(str)), + 'styles': ParameterDefinition(True, cast=ListFactory(str)), + 'crs': ParameterDefinition(True, match='^EPSG:\d+$'), + 'bbox': ParameterDefinition(True, cast=ListFactory(float)), + 'width': ParameterDefinition(True, cast=int), + 'height': ParameterDefinition(True, cast=int), + 'format': ParameterDefinition(True, allowedvalues=('image/gif','image/png','image/jpeg'), cast=str.lower), + 'transparent': ParameterDefinition(False, allowedvalues=('true','false'), cast=str.lower), + 'bgcolor': ParameterDefinition(False, ColorFactory('0xFFFFFF'), cast=ColorFactory), + 'exceptions': ParameterDefinition(False, 'xml', ('xml',), cast=str.lower), + } + } + + CONF_SERVICE = [ + ['title', 'Title', str], + ['abstract', 'Abstract', str], + ['onlineresource', 'OnlineResource', str], + ['fees', 'Fees', str], + ['accessconstraints', 'AccessConstraints', str], + ['layerlimit', 'LayerLimit', int], + ['maxwidth', 'MaxWidth', int], + ['maxheight', 'MaxHeight', int] + ] + + capabilitiesxmltemplate = """ + + + WMS + + + + + text/xml + + + + + + + + + + + + + image/png + image/jpeg + image/gif + + + + + + + + + + + + + + XML + + + A Mapnik WMS Server + A Mapnik WMS Server + + + + + """ + + def __init__(self, configpath, factory, opsonlineresource): + self.factory = factory + self.conf = SafeConfigParser() + self.conf.readfp(open(configpath)) + if self.conf.has_option('service', 'epsg'): + self.epsgcode = self.conf.get('service', 'epsg') + self.proj = Projection(['init=epsg:' + self.epsgcode]) + else: + raise "Missing EPSG code" + if not opsonlineresource.endswith('?'): opsonlineresource += '?' + + capetree = ElementTree.fromstring(self.capabilitiesxmltemplate) + + elements = capetree.findall('{http://www.opengis.net/wms}Capability//{http://www.opengis.net/wms}OnlineResource') + for element in elements: + element.set('{http://www.w3.org/1999/xlink}href', opsonlineresource) + + if len(self.conf.items('service')) > 0: + servicee = capetree.find('{http://www.opengis.net/wms}Service') + for item in self.CONF_SERVICE: + if self.conf.has_option('service', item[0]): + value = self.conf.get('service', item[0]) + try: + item[2](value) + except: + raise ServerConfigurationError('Configuration parameter [%s]->%s has an invalid value: %s.' % ('service', item[0], value)) + if item[0] == 'onlineresource': + element = ElementTree.Element('%s' % item[1]) + servicee.append(element) + element.set('{http://www.w3.org/1999/xlink}href', value) + element.set('{http://www.w3.org/1999/xlink}type', 'simple') + else: + element = ElementTree.Element('%s' % item[1]) + element.text = value + servicee.append(element) + + + rootlayerelem = capetree.find('{http://www.opengis.net/wms}Capability/{http://www.opengis.net/wms}Layer') + + rootlayercrs = rootlayerelem.find('{http://www.opengis.net/wms}CRS') + rootlayercrs.text = 'EPSG:%s' % self.epsgcode + + dict = self.factory() + + for layer in dict['layers']: + layername = ElementTree.Element('Name') + layername.text = layer.name() + layertitle = ElementTree.Element('Title') + layertitle.text = layer.name() + env = layer.envelope() + layerexgbb = ElementTree.Element('EX_GeographicBoundingBox') + ll = self.proj.Inverse(env.minx, env.miny) + ur = self.proj.Inverse(env.maxx, env.maxy) + exgbb_wbl = ElementTree.Element('westBoundLongitude') + exgbb_wbl.text = str(ll[0]) + layerexgbb.append(exgbb_wbl) + exgbb_ebl = ElementTree.Element('eastBoundLongitude') + exgbb_ebl.text = str(ur[0]) + layerexgbb.append(exgbb_ebl) + exgbb_sbl = ElementTree.Element('southBoundLatitude') + exgbb_sbl.text = str(ll[1]) + layerexgbb.append(exgbb_sbl) + exgbb_nbl = ElementTree.Element('northBoundLatitude') + exgbb_nbl.text = str(ur[1]) + layerexgbb.append(exgbb_nbl) + layerbbox = ElementTree.Element('BoundingBox') + layerbbox.set('CRS', 'EPSG:%s' % self.epsgcode) + layerbbox.set('minx', str(env.minx)) + layerbbox.set('miny', str(env.miny)) + layerbbox.set('maxx', str(env.maxx)) + layerbbox.set('maxy', str(env.maxy)) + layere = ElementTree.Element('Layer') + layere.append(layername) + layere.append(layertitle) + layere.append(layerexgbb) + layere.append(layerbbox) + rootlayerelem.append(layere) + + self.capabilities = '\n' + ElementTree.tostring(capetree) + + def getcapabilities(self, params): + response = Response('text/xml', self.capabilities) + return response + + def getmap(self, params): + if params['width'] > int(self.conf.get('service', 'maxwidth')) and height > int(self.conf.get('service', 'maxheight')): + raise OGCException('Bad map size.') + m = Map(params['width'], params['height']) + if params.has_key('transparent') and params['transparent'].lower() == 'false': + m.background = params['bgcolor'] + mo = self.factory() + for layername in params['layers']: + for layer in mo['layers']: + if layer.name() == layername: + for stylename in layer.styles: + if stylename in mo['styles'].keys(): + m.append_style(stylename, mo['styles'][stylename]) + m.layers.append(layer) + m.zoom_to_box(Envelope(params['bbox'][0], params['bbox'][1], params['bbox'][2], params['bbox'][3])) + im = Image(params['width'], params['height']) + render(m, im) + im2 = fromstring('RGBA', (params['width'], params['height']), rawdata(im)) + fh = StringIO() + im2.save(fh, PIL_TYPE_MAPPING[params['format']]) + fh.seek(0) + response = Response(params['format'], fh.read()) + return response + +def ServiceFactory(configpath, factory, onlineresource, version): + if not version: + version = Version('1.3.0') + else: + try: + version = Version(version) + except: + raise OGCException("Parameter 'version' has an illegal value.") + if version >= '1.3.0': + return ServiceHandler130(configpath, factory, onlineresource) + else: + return ServiceHandler111(configpath, factory, onlineresource) + + \ No newline at end of file