OK, final fixes to the WMS stuff, cleaned up documentation, and so on.

Things should be good to go for a first try by the wider community!
This commit is contained in:
Jean-Francois Doyon 2006-04-14 03:45:46 +00:00
parent af55c99fa4
commit b8ac9b1984
9 changed files with 193 additions and 60 deletions

36
INSTALL
View file

@ -22,7 +22,8 @@ First, here is a quick list of the software dependencies:
- libtiff - libtiff
- libz - libz
- libfreetype2 - libfreetype2
- (Optional) PostgreSQL libraries - (Optional) PostgreSQL libraries (For PostGIS support)
- (Optional) PROJ.4 (More on this below)
- Python 1.5.2 or greater to build Mapnik - Python 1.5.2 or greater to build Mapnik
- (Optional) Python 2.2 or greater for the Python language bindings - (Optional) Python 2.2 or greater for the Python language bindings
@ -33,6 +34,7 @@ If your system does NOT have one of these installed, you will need to install th
Also, a minimum of 256MB of RAM is recommended for the build process. Also, a minimum of 256MB of RAM is recommended for the build process.
Building Building
-------- --------
@ -101,6 +103,14 @@ PGSQL_LIBS: Search path for PostgreSQL library files ( /path/to/PGSQL_LIBS )
default: /usr/lib default: /usr/lib
actual: /usr/lib actual: /usr/lib
PROJ_INCLUDES: Search path for PROJ.4 include files ( /path/to/PROJ_INCLUDES )
default: /usr/local/include
actual: /usr/local/include
PROJ_LIBS: Search path for PROJ.4 include files ( /path/to/PROJ_LIBS )
default: /usr/local/lib
actual: /usr/local/lib
PYTHON: Python executable ( /path/to/PYTHON ) PYTHON: Python executable ( /path/to/PYTHON )
default: /usr/bin/python default: /usr/bin/python
actual: /usr/bin/python actual: /usr/bin/python
@ -146,6 +156,30 @@ $PYTHON_PREFIX/lib/python$PYTHON_VERSION/site-packages/mapnik: Python bindings
If you're using the default PREFIX, you will most likely need to be root to perform the install. If you're using the default PREFIX, you will most likely need to be root to perform the install.
A note on projection support
----------------------------
At this time Mapnik's core C++ library and map rendering engine does NOT support on-the-fly cartographic
reprojections.
Mapnik can however be configured to build the Python API to the PROJ.4 library. This provides projection
support through Python, and is used by the WMS ogcserver feature, since that server is written in Python.
Here is an example on how to use it:
>>> from mapnik import Projection
registered datasource : raster
registered datasource : shape
registered datasource : postgis
>>> p = Projection(['init=epsg:42304'])
>>> p.Inverse(12345.245,143225.56)
[-94.825927695613018, 50.290732340975467]
>>>
The Projection() instance provides Inverse() and Forward() methods. For details on the possible parameters,
see the PROJ.4 documentation.
Test Test
---- ----

View file

@ -0,0 +1,53 @@
#
# 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 Version
from exceptions import OGCException, ServerConfigurationError
from wms111 import ServiceHandler as ServiceHandler111
from wms130 import ServiceHandler as ServiceHandler130
def ServiceHandlerFactory(conf, mapfactory, onlineresource, version):
if not version:
version = Version('1.3.0')
else:
version = Version(version)
if version >= '1.3.0':
return ServiceHandler130(conf, mapfactory, onlineresource)
else:
return ServiceHandler111(conf, mapfactory, onlineresource)
class BaseWMSFactory:
def __init__(self):
self.layers = {}
self.styles = {}
def register_layer(self, layer):
layername = layer.name()
if not layername:
raise ServerConfigurationError('There is an un-named layer.')
self.layers[layername] = layer
def register_style(self, name, style):
if not name:
raise ServerConfigurationError('There is an un-named style.')
self.styles[name] = style

View file

@ -25,10 +25,11 @@ environ['PYTHON_EGG_CACHE'] = gettempdir()
import sys import sys
from jon import cgi from jon import cgi
from exceptions import OGCException from exceptions import OGCException, ServerConfigurationError
from wms130 import ExceptionHandler from wms111 import ExceptionHandler as ExceptionHandler111
from lxml import etree as ElementTree from wms130 import ExceptionHandler as ExceptionHandler130
from ConfigParser import SafeConfigParser from ConfigParser import SafeConfigParser
from common import Version
class Handler(cgi.DebugHandler): class Handler(cgi.DebugHandler):
@ -36,29 +37,36 @@ class Handler(cgi.DebugHandler):
conf = SafeConfigParser() conf = SafeConfigParser()
conf.readfp(open(self.configpath)) conf.readfp(open(self.configpath))
self.conf = conf self.conf = conf
mapfactorymodule = __import__(conf.get('server', 'module')) if not conf.has_option('server', 'module'):
self.mapfactory = getattr(mapfactorymodule, 'WMSFactory')() raise ServerConfigurationError('The factory module is not defined in the configuration file.')
try:
mapfactorymodule = __import__(conf.get('server', 'module'))
except ImportError:
raise ServerConfigurationError('The factory module could not be loaded.')
if hasattr(mapfactorymodule, 'WMSFactory'):
self.mapfactory = getattr(mapfactorymodule, 'WMSFactory')()
else:
raise ServerConfigurationError('The factory module does not have a WMSFactory class.')
if conf.has_option('server', 'debug'):
self.debug = int(conf.get('server', 'debug'))
else:
self.debug = 0
def process(self, req): def process(self, req):
exceptionhandler = ExceptionHandler reqparams = lowerparams(req.params)
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']) onlineresource = 'http://%s:%s%s?' % (req.environ['SERVER_NAME'], req.environ['SERVER_PORT'], req.environ['SCRIPT_NAME'])
# try:
if not reqparams.has_key('request'): if not reqparams.has_key('request'):
raise OGCException('Missing request parameter.') raise OGCException('Missing request parameter.')
if reqparams['request'] == 'GetCapabilities' and not reqparams.has_key('service'): if reqparams['request'] == 'GetCapabilities' and not reqparams.has_key('service'):
raise OGCException('Missing service parameter.') raise OGCException('Missing service parameter.')
if reqparams['request'] in ['GetMap', 'GetFeatureInfo']: if reqparams['request'] in ['GetMap', 'GetFeatureInfo']:
reqparams['service'] = 'wms' reqparams['service'] = 'WMS'
service = reqparams['service'].lower()
try: try:
mapnikmodule = __import__('mapnik.ogcserver.' + service) mapnikmodule = __import__('mapnik.ogcserver.' + reqparams['service'])
except: except:
raise OGCException('Unsupported service "%s".' % service) raise OGCException('Unsupported service "%s".' % reqparams['service'])
ServiceHandlerFactory = getattr(mapnikmodule.ogcserver, service).ServiceHandlerFactory ServiceHandlerFactory = getattr(mapnikmodule.ogcserver, reqparams['service']).ServiceHandlerFactory
servicehandler, exceptionhandler = ServiceHandlerFactory(self.conf, self.mapfactory, onlineresource, reqparams.get('version', None)) servicehandler = ServiceHandlerFactory(self.conf, self.mapfactory, onlineresource, reqparams.get('version', None))
if reqparams['request'] not in servicehandler.SERVICE_PARAMS.keys(): if reqparams['request'] not in servicehandler.SERVICE_PARAMS.keys():
raise OGCException('Operation "%s" not supported.' % reqparams['request'], 'OperationNotSupported') raise OGCException('Operation "%s" not supported.' % reqparams['request'], 'OperationNotSupported')
ogcparams = servicehandler.processParameters(reqparams['request'], reqparams) ogcparams = servicehandler.processParameters(reqparams['request'], reqparams)
@ -67,14 +75,25 @@ class Handler(cgi.DebugHandler):
except: except:
raise OGCException('Operation "%s" not supported.' % reqparams['request'], 'OperationNotSupported') raise OGCException('Operation "%s" not supported.' % reqparams['request'], 'OperationNotSupported')
response = requesthandler(ogcparams) response = requesthandler(ogcparams)
# except:
# raise
# else:
req.set_header('Content-Type', response.content_type) req.set_header('Content-Type', response.content_type)
req.write(response.content) req.write(response.content)
"""
except OGCException: def traceback(self, req):
eh = exceptionhandler() reqparams = lowerparams(req.params)
req.set_header('Content-Type', eh.mimetype) version = reqparams.get('version', None)
req.write(ElementTree.tostring(eh.getexcetree(sys.exc_info()[1]))) if not version:
""" version = Version('1.3.0')
else:
version = Version(version)
if version >= '1.3.0':
eh = ExceptionHandler130(self.debug)
else:
eh = ExceptionHandler111(self.debug)
req.set_header('Content-Type', eh.mimetype)
req.write(eh.getcontent())
def lowerparams(params):
reqparams = {}
for key, value in params.items():
reqparams[key.lower()] = value
return reqparams

View file

@ -195,8 +195,6 @@ class CRSFactory:
class WMSBaseServiceHandler(BaseServiceHandler): class WMSBaseServiceHandler(BaseServiceHandler):
def GetMap(self, params): def GetMap(self, params):
if str(params['crs']) != str(self.crs):
raise OGCException('Unsupported CRS requested. Must be "%s" and not "%s".' % (self.crs, params['crs']), 'InvalidCRS')
if params['bbox'][0] >= params['bbox'][2]: if params['bbox'][0] >= params['bbox'][2]:
raise OGCException("BBOX values don't make sense. minx is greater than maxx.") raise OGCException("BBOX values don't make sense. minx is greater than maxx.")
if params['bbox'][1] >= params['bbox'][3]: if params['bbox'][1] >= params['bbox'][3]:
@ -204,18 +202,19 @@ class WMSBaseServiceHandler(BaseServiceHandler):
m = Map(params['width'], params['height']) m = Map(params['width'], params['height'])
if params.has_key('transparent') and params['transparent'] == 'FALSE': if params.has_key('transparent') and params['transparent'] == 'FALSE':
m.background = params['bgcolor'] m.background = params['bgcolor']
maplayers = self.mapfactory.getlayers() maplayers = self.mapfactory.layers
mapstyles = self.mapfactory.getstyles() mapstyles = self.mapfactory.styles
for layername in params['layers']: for layername in params['layers']:
for layer in maplayers: try:
if layer.name() == layername: layer = maplayers[layername]
for stylename in layer.styles: except KeyError:
if stylename in mapstyles.keys(): raise OGCException('Layer not defined: %s.' % layername, 'LayerNotDefined')
m.append_style(stylename, mapstyles[stylename]) for stylename in layer.styles:
m.layers.append(layer) if stylename in mapstyles.keys():
if len(m.layers) != len(params['layers']): m.append_style(stylename, mapstyles[stylename])
badnames = [ layername for layername in params['layers'] if layername not in [ layer.name() for layer in m.layers ] ] else:
raise OGCException('The following layers are not defined by this server: %s.' % ','.join(badnames), 'LayerNotDefined') raise ServerConfigurationError('Layer "%s" refers to non-existent style "%s".' % (layername, stylename))
m.layers.append(layer)
m.zoom_to_box(Envelope(params['bbox'][0], params['bbox'][1], params['bbox'][2], params['bbox'][3])) m.zoom_to_box(Envelope(params['bbox'][0], params['bbox'][1], params['bbox'][2], params['bbox'][3]))
im = Image(params['width'], params['height']) im = Image(params['width'], params['height'])
render(m, im) render(m, im)

View file

@ -20,6 +20,10 @@
# $Id$ # $Id$
from copy import deepcopy from copy import deepcopy
from lxml import etree as ElementTree
from StringIO import StringIO
from traceback import print_tb
from sys import exc_info
class OGCException(Exception): class OGCException(Exception):
pass pass
@ -29,11 +33,20 @@ class ServerConfigurationError(Exception):
class BaseExceptionHandler: class BaseExceptionHandler:
def getexcetree(self, exc): def __init__(self, debug):
self.debug = debug
def getcontent(self):
excinfo = exc_info()
ogcexcetree = deepcopy(self.xmltemplate) ogcexcetree = deepcopy(self.xmltemplate)
e = ogcexcetree.find(self.xpath) e = ogcexcetree.find(self.xpath)
if len(exc.args) > 0: if self.debug:
e.text = exc.args[0] fh = StringIO()
if len(exc.args) > 1: print_tb(excinfo[2], None, fh)
e.set('code', exc.args[1]) fh.seek(0)
return ogcexcetree e.text = '\n' + fh.read() + '\n' + str(excinfo[0]) + ': ' + ', '.join(excinfo[1].args) + '\n'
elif len(excinfo[1].args) > 0:
e.text = excinfo[1].args[0]
if isinstance(excinfo[1], OGCException) and len(excinfo[1].args) > 1:
e.set('code', excinfo[1].args[1])
return ElementTree.tostring(ogcexcetree)

View file

@ -140,7 +140,7 @@ class ServiceHandler(WMSBaseServiceHandler):
rootlayersrs = rootlayerelem.find('SRS') rootlayersrs = rootlayerelem.find('SRS')
rootlayersrs.text = str(self.crs) rootlayersrs.text = str(self.crs)
for layer in self.mapfactory.getlayers(): for layer in self.mapfactory.layers.values():
layername = ElementTree.Element('Name') layername = ElementTree.Element('Name')
layername.text = layer.name() layername.text = layer.name()
layertitle = ElementTree.Element('Title') layertitle = ElementTree.Element('Title')
@ -172,6 +172,11 @@ class ServiceHandler(WMSBaseServiceHandler):
response = Response('application/vnd.ogc.wms_xml', self.capabilities) response = Response('application/vnd.ogc.wms_xml', self.capabilities)
return response return response
def GetMap(self, params):
if str(params['srs']) != str(self.crs):
raise OGCException('Unsupported SRS requested. Must be "%s" and not "%s".' % (self.crs, params['crs']), 'InvalidCRS')
return WMSBaseServiceHandler.GetMap(self, params)
class ExceptionHandler(BaseExceptionHandler): class ExceptionHandler(BaseExceptionHandler):
mimetype = "application/vnd.ogc.se_xml" mimetype = "application/vnd.ogc.se_xml"

View file

@ -126,7 +126,7 @@ class ServiceHandler(WMSBaseServiceHandler):
servicee = capetree.find('{http://www.opengis.net/wms}Service') servicee = capetree.find('{http://www.opengis.net/wms}Service')
for item in self.CONF_SERVICE: for item in self.CONF_SERVICE:
if self.conf.has_option('service', item[0]): if self.conf.has_option('service', item[0]):
value = self.conf.get('service', item[0]) value = self.conf.get('service', item[0]).strip()
try: try:
item[2](value) item[2](value)
except: except:
@ -147,7 +147,7 @@ class ServiceHandler(WMSBaseServiceHandler):
rootlayercrs = rootlayerelem.find('{http://www.opengis.net/wms}CRS') rootlayercrs = rootlayerelem.find('{http://www.opengis.net/wms}CRS')
rootlayercrs.text = str(self.crs) rootlayercrs.text = str(self.crs)
for layer in self.mapfactory.getlayers(): for layer in self.mapfactory.layers.values():
layername = ElementTree.Element('Name') layername = ElementTree.Element('Name')
layername.text = layer.name() layername.text = layer.name()
layertitle = ElementTree.Element('Title') layertitle = ElementTree.Element('Title')
@ -190,6 +190,8 @@ class ServiceHandler(WMSBaseServiceHandler):
def GetMap(self, params): def GetMap(self, params):
if params['width'] > int(self.conf.get('service', 'maxwidth')) or params['height'] > int(self.conf.get('service', 'maxheight')): 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.') raise OGCException('Requested map size exceeds limits set by this server.')
if str(params['crs']) != str(self.crs):
raise OGCException('Unsupported CRS requested. Must be "%s" and not "%s".' % (self.crs, params['crs']), 'InvalidCRS')
return WMSBaseServiceHandler.GetMap(self, params) return WMSBaseServiceHandler.GetMap(self, params)
class ExceptionHandler(BaseExceptionHandler): class ExceptionHandler(BaseExceptionHandler):

View file

@ -89,15 +89,27 @@ API, look in demo/python, or in docs/epydocs.
The server needs a python module, with code that looks like this: The server needs a python module, with code that looks like this:
from mapnik.ogcserver.wms import BaseWMSFactory from mapnik.ogcserver.WMS import BaseWMSFactory
class MapFactory: class WMSFactory(BaseWMSFactory):
def __init(self): def __init(self):
BaseWMSFactory.__init__(self)
sty = Style()
... ...
self.register_style('stylename', sty)
def getlayers(self):
lyr = Layer(name='layername')
... ...
self.register_layer(lyr)
def getstyles(self):
... The rules for writing this class are:
- It MUST be called 'WMSFactory'.
- It MUST sub-class mapnik.ogcserver.WMS.BaseWMSFactory.
- The __init__ MUST call the base class'.
- Layers MUST be named with the 'name' parameter to the constructor.
- style and layer names are meant for machine readability, not human. Keep
them short and simple, without spaces or special characters.
- The layers must have at least one style associated with them (a default).
- No Map() object is used or needed here.

View file

@ -36,8 +36,4 @@ epsg=4326
# supporting the service for example. This is NOT the online # supporting the service for example. This is NOT the online
# resource pointing to the CGI. # resource pointing to the CGI.
onlineresource=http://www.mapnik.org/ onlineresource=http://www.mapnik.org/
[contact]
name=
email=