- 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:
parent
80af11ba3a
commit
46f9b02394
7 changed files with 87 additions and 64 deletions
|
@ -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!')
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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())
|
|
@ -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'
|
|
@ -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'
|
|
@ -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" {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue