- Rigidified parameter validation

- Fixed BLANK exception handling bug
- Added STYLES/LAYERS correlation check
- Finalized ogcserver readme.txt
- Got rid of a compiler warning with PyProjection
- Some coding style edits
This commit is contained in:
Jean-Francois Doyon 2006-05-11 04:24:51 +00:00
parent 80af11ba3a
commit 46f9b02394
7 changed files with 87 additions and 64 deletions

View file

@ -37,11 +37,11 @@ def ServiceHandlerFactory(conf, mapfactory, onlineresource, version):
return ServiceHandler111(conf, mapfactory, onlineresource)
class BaseWMSFactory:
def __init__(self):
self.layers = {}
self.styles = {}
def register_layer(self, layer, extrastyles=()):
layername = layer.name()
if not layername:
@ -51,14 +51,14 @@ class BaseWMSFactory:
layer.wmsextrastyles = extrastyles
else:
raise ServerConfigurationError('Layer "%s" was passed an invalid list of extra styles. List must be a tuple of strings.' % layername)
def register_style(self, name, style):
if not name:
raise ServerConfigurationError('Attempted to register a style without providing a name.')
if not isinstance(style, Style):
raise ServerConfigurationError('Bad style object passed to register_style() for style "%s".' % name)
self.styles[name] = style
def finalize(self):
if len(self.layers) == 0:
raise ServerConfigurationError('No layers defined!')

View file

@ -57,23 +57,30 @@ class Handler(cgi.DebugHandler):
onlineresource = 'http://%s:%s%s?' % (req.environ['SERVER_NAME'], req.environ['SERVER_PORT'], req.environ['SCRIPT_NAME'])
if not reqparams.has_key('request'):
raise OGCException('Missing request parameter.')
if reqparams['request'] == 'GetCapabilities' and not reqparams.has_key('service'):
request = reqparams['request']
del reqparams['request']
if request == 'GetCapabilities' and not reqparams.has_key('service'):
raise OGCException('Missing service parameter.')
if reqparams['request'] in ['GetMap', 'GetFeatureInfo']:
reqparams['service'] = 'WMS'
if request in ['GetMap', 'GetFeatureInfo']:
service = 'WMS'
else:
service = reqparams['service']
if reqparams.has_key('service'):
del reqparams['service']
try:
mapnikmodule = __import__('mapnik.ogcserver.' + reqparams['service'])
mapnikmodule = __import__('mapnik.ogcserver.' + service)
except:
raise OGCException('Unsupported service "%s".' % reqparams['service'])
ServiceHandlerFactory = getattr(mapnikmodule.ogcserver, reqparams['service']).ServiceHandlerFactory
raise OGCException('Unsupported service "%s".' % service)
ServiceHandlerFactory = getattr(mapnikmodule.ogcserver, service).ServiceHandlerFactory
servicehandler = ServiceHandlerFactory(self.conf, self.mapfactory, onlineresource, reqparams.get('version', None))
if reqparams['request'] not in servicehandler.SERVICE_PARAMS.keys():
raise OGCException('Operation "%s" not supported.' % reqparams['request'], 'OperationNotSupported')
ogcparams = servicehandler.processParameters(reqparams['request'], reqparams)
del reqparams['version']
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, reqparams['request'])
requesthandler = getattr(servicehandler, request)
except:
raise OGCException('Operation "%s" not supported.' % reqparams['request'], 'OperationNotSupported')
raise OGCException('Operation "%s" not supported.' % request, 'OperationNotSupported')
response = requesthandler(ogcparams)
req.set_header('Content-Type', response.content_type)
req.write(response.content)

View file

@ -46,21 +46,21 @@ class ParameterDefinition:
@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 mandatory not in [True, False]:
@ -81,6 +81,9 @@ class BaseServiceHandler:
def processParameters(self, requestname, params):
finalparams = {}
for paramname in params.keys():
if paramname not in self.SERVICE_PARAMS[requestname].keys():
raise OGCException('Unknown request parameter "%s".' % paramname)
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)
@ -144,10 +147,10 @@ class Version:
return 0
class ListFactory:
def __init__(self, cast):
self.cast = cast
def __call__(self, string):
seq = string.split(',')
return map(self.cast, seq)
@ -159,15 +162,15 @@ def ColorFactory(colorstring):
raise OGCException('Invalid color value. Must be of format "0xFFFFFF".')
class CRS:
def __init__(self, namespace, code):
self.namespace = namespace
self.code = int(code)
self.proj = None
def __repr__(self):
return '%s:%s' % (self.namespace, self.code)
def __eq__(self, other):
if str(other) == str(self):
return True
@ -177,17 +180,17 @@ class CRS:
if not self.proj:
self.proj = Projection(['init=%s' % str(self).lower()])
return self.proj.Inverse(x, y)
def Forward(self, x, y):
if not self.proj:
self.proj = Projection(['init=%s' % str(self).lower()])
return self.proj.Forward(x, y)
class CRSFactory:
def __init__(self, allowednamespaces):
self.allowednamespaces = allowednamespaces
def __call__(self, crsstring):
if not re.match('^[A-Z]{3,5}:\d+$', crsstring):
raise OGCException('Invalid format for the CRS parameter: %s' % crsstring, 'InvalidCRS')
@ -198,12 +201,14 @@ class CRSFactory:
raise OGCException('Invalid CRS Namespace: %s' % crsparts[0], 'InvalidCRS')
class WMSBaseServiceHandler(BaseServiceHandler):
def GetMap(self, params):
if params['bbox'][0] >= params['bbox'][2]:
raise OGCException("BBOX values don't make sense. minx is greater than maxx.")
if params['bbox'][1] >= params['bbox'][3]:
raise OGCException("BBOX values don't make sense. miny is greater than maxy.")
if params.has_key('styles') and len(params['styles']) != len(params['layers']):
raise OGCException('STYLES length does not match LAYERS length.')
m = Map(params['width'], params['height'])
if params.has_key('transparent') and params['transparent'] == 'FALSE':
m.background = params['bgcolor']
@ -225,7 +230,6 @@ class WMSBaseServiceHandler(BaseServiceHandler):
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)
render_to_file(m, '/tmp/ogcserver.png', 'png')
im = fromstring('RGBA', (params['width'], params['height']), rawdata(im))
fh = StringIO()
im.save(fh, PIL_TYPE_MAPPING[params['format']], quality=100)
@ -233,10 +237,10 @@ class WMSBaseServiceHandler(BaseServiceHandler):
return Response(params['format'], fh.read())
class BaseExceptionHandler:
def __init__(self, debug):
self.debug = debug
def getresponse(self, params):
code = ''
message = ''
@ -252,10 +256,10 @@ class BaseExceptionHandler:
if isinstance(excinfo[1], OGCException) and len(excinfo[1].args) > 1:
code = excinfo[1].args[1]
exceptions = params.get('exceptions', None)
if not exceptions:
if not exceptions or not self.handlers.has_key(exceptions):
exceptions = self.defaulthandler
return self.handlers[exceptions](self, code, message, params)
def xmlhandler(self, code, message, params):
ogcexcetree = deepcopy(self.xmltemplate)
e = ogcexcetree.find(self.xpath)
@ -274,10 +278,10 @@ class BaseExceptionHandler:
im.save(fh, PIL_TYPE_MAPPING[params['format']])
fh.seek(0)
return Response(params['format'], fh.read())
def blankhandler(self, code, message, params):
bgcolor = params.get('bgcolor', '#FFFFFF')
bgcolor.replace('0x', '#')
bgcolor = bgcolor.replace('0x', '#')
transparent = params.get('transparent', 'FALSE')
if transparent == 'TRUE':
im = new('RGBA', (int(params['width']), int(params['height'])))
@ -287,4 +291,4 @@ class BaseExceptionHandler:
fh = StringIO()
im.save(fh, PIL_TYPE_MAPPING[params['format']])
fh.seek(0)
return Response(params['format'], fh.read())
return Response(params['format'], fh.read())

View file

@ -32,17 +32,16 @@ class ServiceHandler(WMSBaseServiceHandler):
'updatesequence': ParameterDefinition(False, str)
},
'GetMap': {
'version': ParameterDefinition(True, Version, allowedvalues=(Version('1.1.1'),)),
'layers': ParameterDefinition(True, ListFactory(str)),
'styles': ParameterDefinition(True, ListFactory(str)),
'srs': ParameterDefinition(True, CRSFactory(['EPSG'])),
'bbox': ParameterDefinition(True, ListFactory(float)),
'width': ParameterDefinition(True, int),
'height': ParameterDefinition(True, int),
'format': ParameterDefinition(True, str, allowedvalues=('image/png', 'image/jpeg', 'image/gif')),
'format': ParameterDefinition(True, str, allowedvalues=('image/png', 'image/jpeg')),
'transparent': ParameterDefinition(False, str, 'FALSE', ('TRUE', 'FALSE')),
'bgcolor': ParameterDefinition(False, ColorFactory, ColorFactory('0xFFFFFF')),
'exceptions': ParameterDefinition(False, str, 'application/vnd.ogc.se_xml', ('application/vnd.ogc.se_xml', 'application/vnd.ogc.se_inimage'))
'exceptions': ParameterDefinition(False, str, 'application/vnd.ogc.se_xml', ('application/vnd.ogc.se_xml', 'application/vnd.ogc.se_inimage', 'application/vnd.ogc.se_blank'))
}
}
@ -113,7 +112,7 @@ class ServiceHandler(WMSBaseServiceHandler):
ServerConfigurationError('EPSG code not properly configured.')
capetree = ElementTree.fromstring(self.capabilitiesxmltemplate)
elements = capetree.findall('Capability//OnlineResource')
for element in elements:
element.set('{http://www.w3.org/1999/xlink}href', opsonlineresource)
@ -138,7 +137,7 @@ class ServiceHandler(WMSBaseServiceHandler):
servicee.append(element)
rootlayerelem = capetree.find('Capability/Layer')
rootlayersrs = rootlayerelem.find('SRS')
rootlayersrs.text = str(self.crs)
@ -177,7 +176,7 @@ class ServiceHandler(WMSBaseServiceHandler):
style.append(styletitle)
layere.append(style)
rootlayerelem.append(layere)
self.capabilities = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n' + ElementTree.tostring(capetree)
def GetCapabilities(self, params):
@ -190,20 +189,20 @@ class ServiceHandler(WMSBaseServiceHandler):
return WMSBaseServiceHandler.GetMap(self, params)
class ExceptionHandler(BaseExceptionHandler):
xmlmimetype = "application/vnd.ogc.se_xml"
xmltemplate = ElementTree.fromstring("""<?xml version='1.0' encoding="UTF-8" standalone="no"?>
<!DOCTYPE ServiceExceptionReport SYSTEM "http://www.digitalearth.gov/wmt/xml/exception_1_1_1.dtd">
<ServiceExceptionReport version="1.1.1">
<ServiceException />
</ServiceExceptionReport>
""")
xpath = 'ServiceException'
handlers = {'application/vnd.ogc.se_xml': BaseExceptionHandler.xmlhandler,
'application/vnd.ogc.se_inimage': BaseExceptionHandler.inimagehandler,
'application/vnd.ogc.se_blank': BaseExceptionHandler.blankhandler}
defaulthandler = 'application/vnd.ogc.se_xml'

View file

@ -33,17 +33,16 @@ class ServiceHandler(WMSBaseServiceHandler):
'updatesequence': ParameterDefinition(False, str)
},
'GetMap': {
'version': ParameterDefinition(True, Version, allowedvalues=(Version('1.3.0'),)),
'layers': ParameterDefinition(True, ListFactory(str)),
'styles': ParameterDefinition(True, ListFactory(str)),
'crs': ParameterDefinition(True, CRSFactory(['EPSG'])),
'bbox': ParameterDefinition(True, ListFactory(float)),
'width': ParameterDefinition(True, int),
'height': ParameterDefinition(True, int),
'format': ParameterDefinition(True, str, allowedvalues=('image/gif','image/png', 'image/jpeg')),
'format': ParameterDefinition(True, str, allowedvalues=('image/png', 'image/jpeg')),
'transparent': ParameterDefinition(False, str, 'FALSE', ('TRUE', 'FALSE')),
'bgcolor': ParameterDefinition(False, ColorFactory, ColorFactory('0xFFFFFF')),
'exceptions': ParameterDefinition(False, str, 'XML', ('XML', 'INIMAGE')),
'exceptions': ParameterDefinition(False, str, 'XML', ('XML', 'INIMAGE', 'BLANK')),
}
}
@ -119,7 +118,7 @@ class ServiceHandler(WMSBaseServiceHandler):
raise ServerConfigurationError('EPSG code not properly configured.')
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)
@ -143,12 +142,11 @@ class ServiceHandler(WMSBaseServiceHandler):
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 = str(self.crs)
for layer in self.mapfactory.layers.values():
layername = ElementTree.Element('Name')
layername.text = layer.name()
@ -192,13 +190,13 @@ class ServiceHandler(WMSBaseServiceHandler):
style.append(styletitle)
layere.append(style)
rootlayerelem.append(layere)
self.capabilities = '<?xml version="1.0" encoding="UTF-8"?>' + 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')) or params['height'] > int(self.conf.get('service', 'maxheight')):
raise OGCException('Requested map size exceeds limits set by this server.')
@ -207,9 +205,9 @@ class ServiceHandler(WMSBaseServiceHandler):
return WMSBaseServiceHandler.GetMap(self, params)
class ExceptionHandler(BaseExceptionHandler):
xmlmimetype = "text/xml"
xmltemplate = ElementTree.fromstring("""<?xml version='1.0' encoding="UTF-8"?>
<ServiceExceptionReport version="1.3.0"
xmlns="http://www.opengis.net/ogc"
@ -218,11 +216,11 @@ class ExceptionHandler(BaseExceptionHandler):
<ServiceException/>
</ServiceExceptionReport>
""")
xpath = '{http://www.opengis.net/ogc}ServiceException'
handlers = {'XML': BaseExceptionHandler.xmlhandler,
'INIMAGE': BaseExceptionHandler.inimagehandler,
'BLANK': BaseExceptionHandler.blankhandler}
defaulthandler = 'XML'

View file

@ -24,6 +24,7 @@
* this copyright message remains intact.
************************************************************************/
#include "Python.h"
#include <string.h>
#if defined(_WIN32) || defined(__WIN32__)
@ -173,7 +174,6 @@ SWIG_TypeQuery(const char *name) {
************************************************************************/
#include <stdlib.h>
#include "Python.h"
#ifdef __cplusplus
extern "C" {

View file

@ -23,6 +23,7 @@ Features/Caveats
- XML/INIMAGE/BLANK error handling
- No real layer metadata support yet
- No re-projection support
- Needs to be able to write to tempfile.gettempdir() (most likely "/tmp")
Dependencies
@ -101,7 +102,8 @@ class WMSFactory(BaseWMSFactory):
lyr = Layer(name='layername')
...
self.register_layer(lyr, ('extra', 'style', 'names'))
lyr.styles.append('stylename')
self.register_layer(lyr)
self.finalize()
The rules for writing this class are:
@ -116,9 +118,22 @@ The rules for writing this class are:
- No Map() object is used or needed here.
- Be sure to call self.finalize() once you've registered everything! This will
validate everything and let you know if there's problems.
- You can associate more styles to a given layer by passing a tuple of style
names along with the layer itself to register_layer(). As of this writing
this is useless as the core engine doesn't yet support named styles :)
- Be sure to associate a default style with the layer. In the future, there
will be a mechanism to assign other non-default styles to a layer, to support
named styles through the STYLES= parameter.
To Do
-----
- Named style support.
- Improve configuration to allow for full server metadata.
- Add support for richer layer metadata.
- Investigate moving to cElementTree from lxml.
- Add some internal "caching" for performance improvements.
- Support GetFeatureInfo (Requires core changes).
- Switch to using C/C++ libs for image generation, instead of PIL (also
requires core changes). PIL requirement will remain for INIMAGE/BLANK
error handling.
Conclusion