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:
parent
af55c99fa4
commit
b8ac9b1984
9 changed files with 193 additions and 60 deletions
36
INSTALL
36
INSTALL
|
@ -22,7 +22,8 @@ First, here is a quick list of the software dependencies:
|
|||
- libtiff
|
||||
- libz
|
||||
- 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
|
||||
- (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.
|
||||
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
|
@ -101,6 +103,14 @@ PGSQL_LIBS: Search path for PostgreSQL library files ( /path/to/PGSQL_LIBS )
|
|||
default: /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 )
|
||||
default: /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.
|
||||
|
||||
|
||||
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
|
||||
----
|
||||
|
||||
|
|
53
bindings/python/mapnik/ogcserver/WMS.py
Normal file
53
bindings/python/mapnik/ogcserver/WMS.py
Normal 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
|
|
@ -25,10 +25,11 @@ environ['PYTHON_EGG_CACHE'] = gettempdir()
|
|||
|
||||
import sys
|
||||
from jon import cgi
|
||||
from exceptions import OGCException
|
||||
from wms130 import ExceptionHandler
|
||||
from lxml import etree as ElementTree
|
||||
from exceptions import OGCException, ServerConfigurationError
|
||||
from wms111 import ExceptionHandler as ExceptionHandler111
|
||||
from wms130 import ExceptionHandler as ExceptionHandler130
|
||||
from ConfigParser import SafeConfigParser
|
||||
from common import Version
|
||||
|
||||
class Handler(cgi.DebugHandler):
|
||||
|
||||
|
@ -36,29 +37,36 @@ class Handler(cgi.DebugHandler):
|
|||
conf = SafeConfigParser()
|
||||
conf.readfp(open(self.configpath))
|
||||
self.conf = conf
|
||||
mapfactorymodule = __import__(conf.get('server', 'module'))
|
||||
self.mapfactory = getattr(mapfactorymodule, 'WMSFactory')()
|
||||
if not conf.has_option('server', 'module'):
|
||||
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):
|
||||
exceptionhandler = ExceptionHandler
|
||||
reqparams = {}
|
||||
for key, value in req.params.items():
|
||||
reqparams[key.lower()] = value
|
||||
reqparams = lowerparams(req.params)
|
||||
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.')
|
||||
if reqparams['request'] == 'GetCapabilities' and not reqparams.has_key('service'):
|
||||
raise OGCException('Missing service parameter.')
|
||||
if reqparams['request'] in ['GetMap', 'GetFeatureInfo']:
|
||||
reqparams['service'] = 'wms'
|
||||
service = reqparams['service'].lower()
|
||||
reqparams['service'] = 'WMS'
|
||||
try:
|
||||
mapnikmodule = __import__('mapnik.ogcserver.' + service)
|
||||
mapnikmodule = __import__('mapnik.ogcserver.' + reqparams['service'])
|
||||
except:
|
||||
raise OGCException('Unsupported service "%s".' % service)
|
||||
ServiceHandlerFactory = getattr(mapnikmodule.ogcserver, service).ServiceHandlerFactory
|
||||
servicehandler, exceptionhandler = ServiceHandlerFactory(self.conf, self.mapfactory, onlineresource, reqparams.get('version', None))
|
||||
raise OGCException('Unsupported service "%s".' % reqparams['service'])
|
||||
ServiceHandlerFactory = getattr(mapnikmodule.ogcserver, reqparams['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)
|
||||
|
@ -67,14 +75,25 @@ class Handler(cgi.DebugHandler):
|
|||
except:
|
||||
raise OGCException('Operation "%s" not supported.' % reqparams['request'], 'OperationNotSupported')
|
||||
response = requesthandler(ogcparams)
|
||||
# except:
|
||||
# raise
|
||||
# else:
|
||||
req.set_header('Content-Type', response.content_type)
|
||||
req.write(response.content)
|
||||
"""
|
||||
except OGCException:
|
||||
eh = exceptionhandler()
|
||||
req.set_header('Content-Type', eh.mimetype)
|
||||
req.write(ElementTree.tostring(eh.getexcetree(sys.exc_info()[1])))
|
||||
"""
|
||||
|
||||
def traceback(self, req):
|
||||
reqparams = lowerparams(req.params)
|
||||
version = reqparams.get('version', None)
|
||||
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
|
|
@ -195,8 +195,6 @@ class CRSFactory:
|
|||
class WMSBaseServiceHandler(BaseServiceHandler):
|
||||
|
||||
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]:
|
||||
raise OGCException("BBOX values don't make sense. minx is greater than maxx.")
|
||||
if params['bbox'][1] >= params['bbox'][3]:
|
||||
|
@ -204,18 +202,19 @@ class WMSBaseServiceHandler(BaseServiceHandler):
|
|||
m = Map(params['width'], params['height'])
|
||||
if params.has_key('transparent') and params['transparent'] == 'FALSE':
|
||||
m.background = params['bgcolor']
|
||||
maplayers = self.mapfactory.getlayers()
|
||||
mapstyles = self.mapfactory.getstyles()
|
||||
maplayers = self.mapfactory.layers
|
||||
mapstyles = self.mapfactory.styles
|
||||
for layername in params['layers']:
|
||||
for layer in maplayers:
|
||||
if layer.name() == layername:
|
||||
for stylename in layer.styles:
|
||||
if stylename in mapstyles.keys():
|
||||
m.append_style(stylename, mapstyles[stylename])
|
||||
m.layers.append(layer)
|
||||
if len(m.layers) != len(params['layers']):
|
||||
badnames = [ layername for layername in params['layers'] if layername not in [ layer.name() for layer in m.layers ] ]
|
||||
raise OGCException('The following layers are not defined by this server: %s.' % ','.join(badnames), 'LayerNotDefined')
|
||||
try:
|
||||
layer = maplayers[layername]
|
||||
except KeyError:
|
||||
raise OGCException('Layer not defined: %s.' % layername, 'LayerNotDefined')
|
||||
for stylename in layer.styles:
|
||||
if stylename in mapstyles.keys():
|
||||
m.append_style(stylename, mapstyles[stylename])
|
||||
else:
|
||||
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]))
|
||||
im = Image(params['width'], params['height'])
|
||||
render(m, im)
|
||||
|
|
|
@ -20,6 +20,10 @@
|
|||
# $Id$
|
||||
|
||||
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):
|
||||
pass
|
||||
|
@ -29,11 +33,20 @@ class ServerConfigurationError(Exception):
|
|||
|
||||
class BaseExceptionHandler:
|
||||
|
||||
def getexcetree(self, exc):
|
||||
def __init__(self, debug):
|
||||
self.debug = debug
|
||||
|
||||
def getcontent(self):
|
||||
excinfo = exc_info()
|
||||
ogcexcetree = deepcopy(self.xmltemplate)
|
||||
e = ogcexcetree.find(self.xpath)
|
||||
if len(exc.args) > 0:
|
||||
e.text = exc.args[0]
|
||||
if len(exc.args) > 1:
|
||||
e.set('code', exc.args[1])
|
||||
return ogcexcetree
|
||||
if self.debug:
|
||||
fh = StringIO()
|
||||
print_tb(excinfo[2], None, fh)
|
||||
fh.seek(0)
|
||||
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)
|
|
@ -140,7 +140,7 @@ class ServiceHandler(WMSBaseServiceHandler):
|
|||
rootlayersrs = rootlayerelem.find('SRS')
|
||||
rootlayersrs.text = str(self.crs)
|
||||
|
||||
for layer in self.mapfactory.getlayers():
|
||||
for layer in self.mapfactory.layers.values():
|
||||
layername = ElementTree.Element('Name')
|
||||
layername.text = layer.name()
|
||||
layertitle = ElementTree.Element('Title')
|
||||
|
@ -172,6 +172,11 @@ class ServiceHandler(WMSBaseServiceHandler):
|
|||
response = Response('application/vnd.ogc.wms_xml', self.capabilities)
|
||||
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):
|
||||
|
||||
mimetype = "application/vnd.ogc.se_xml"
|
||||
|
|
|
@ -126,7 +126,7 @@ class ServiceHandler(WMSBaseServiceHandler):
|
|||
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])
|
||||
value = self.conf.get('service', item[0]).strip()
|
||||
try:
|
||||
item[2](value)
|
||||
except:
|
||||
|
@ -147,7 +147,7 @@ class ServiceHandler(WMSBaseServiceHandler):
|
|||
rootlayercrs = rootlayerelem.find('{http://www.opengis.net/wms}CRS')
|
||||
rootlayercrs.text = str(self.crs)
|
||||
|
||||
for layer in self.mapfactory.getlayers():
|
||||
for layer in self.mapfactory.layers.values():
|
||||
layername = ElementTree.Element('Name')
|
||||
layername.text = layer.name()
|
||||
layertitle = ElementTree.Element('Title')
|
||||
|
@ -190,6 +190,8 @@ class ServiceHandler(WMSBaseServiceHandler):
|
|||
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.')
|
||||
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)
|
||||
|
||||
class ExceptionHandler(BaseExceptionHandler):
|
||||
|
|
|
@ -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:
|
||||
|
||||
from mapnik.ogcserver.wms import BaseWMSFactory
|
||||
from mapnik.ogcserver.WMS import BaseWMSFactory
|
||||
|
||||
class MapFactory:
|
||||
class WMSFactory(BaseWMSFactory):
|
||||
|
||||
def __init(self):
|
||||
BaseWMSFactory.__init__(self)
|
||||
sty = Style()
|
||||
...
|
||||
|
||||
def getlayers(self):
|
||||
self.register_style('stylename', sty)
|
||||
|
||||
lyr = Layer(name='layername')
|
||||
...
|
||||
|
||||
def getstyles(self):
|
||||
...
|
||||
self.register_layer(lyr)
|
||||
|
||||
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.
|
|
@ -36,8 +36,4 @@ epsg=4326
|
|||
# supporting the service for example. This is NOT the online
|
||||
# resource pointing to the CGI.
|
||||
|
||||
onlineresource=http://www.mapnik.org/
|
||||
|
||||
[contact]
|
||||
name=
|
||||
email=
|
||||
onlineresource=http://www.mapnik.org/
|
Loading…
Reference in a new issue