- Add projection support to the WMS server and edit ogcserver readme accordingly.

NOTE: WMS 1.1.1 not tested, WMS 1.3.0 lightly tested.
This commit is contained in:
Jean-Francois Doyon 2006-10-19 01:25:24 +00:00
parent ce1353553c
commit 85f1d27a12
5 changed files with 58 additions and 51 deletions

View file

@ -24,6 +24,7 @@ from exceptions import OGCException, ServerConfigurationError
from wms111 import ServiceHandler as ServiceHandler111
from wms130 import ServiceHandler as ServiceHandler130
from mapnik import Style, Layer
import re
def ServiceHandlerFactory(conf, mapfactory, onlineresource, version):
@ -46,6 +47,8 @@ class BaseWMSFactory:
layername = layer.name
if not layername:
raise ServerConfigurationError('Attempted to register an unnamed layer.')
if not re.match('^\+init=epsg:\d+$', layer.srs):
raise ServerConfigurationError('Attempted to register a layer without an epsg projection defined.')
if defaultstyle not in self.styles.keys() + self.aggregatestyles.keys():
raise ServerConfigurationError('Attempted to register a layer with an non-existent default style.')
layer.wmsdefaultstyle = defaultstyle

View file

@ -20,7 +20,7 @@
# $Id$
from exceptions import OGCException, ServerConfigurationError
from mapnik import Map, Color, Envelope, render, rawdata, Image, Projection, render_to_file, Coord
from mapnik import Map, Color, Envelope, render, rawdata, Image, Projection as MapnikProjection, render_to_file, Coord
from PIL.Image import fromstring, new
from PIL.ImageDraw import Draw
from StringIO import StringIO
@ -241,7 +241,7 @@ def ColorFactory(colorstring):
class CRS:
def __init__(self, namespace, code):
self.namespace = namespace
self.namespace = namespace.lower()
self.code = int(code)
self.proj = None
@ -255,12 +255,12 @@ class CRS:
def inverse(self, x, y):
if not self.proj:
self.proj = Projection('+init=%s' % str(self).lower())
self.proj = Projection('+init=%s:%s' % (self.namespace, self.code))
return self.proj.inverse(Coord(x, y))
def forward(self, x, y):
if not self.proj:
self.proj = Projection('+init=%s' % str(self).lower())
self.proj = Projection('+init=%s:%s' % (self.namespace, self.code))
return self.proj.forward(Coord(x, y))
class CRSFactory:
@ -286,7 +286,7 @@ class WMSBaseServiceHandler(BaseServiceHandler):
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'])
m = Map(params['width'], params['height'], '+init=%s' % params['crs'])
if params.has_key('transparent') and params['transparent'] == 'FALSE':
m.background = params['bgcolor']
else:
@ -380,3 +380,8 @@ class BaseExceptionHandler:
im.save(fh, PIL_TYPE_MAPPING[params['format']])
fh.seek(0)
return Response(params['format'], fh.read())
class Projection(MapnikProjection):
def epsgstring(self):
return self.params().split('=')[1].upper()

View file

@ -21,9 +21,10 @@
from common import ParameterDefinition, Response, Version, ListFactory, \
ColorFactory, CRSFactory, WMSBaseServiceHandler, CRS, \
BaseExceptionHandler
BaseExceptionHandler, Projection
from exceptions import OGCException, ServerConfigurationError
from lxml import etree as ElementTree
from mapnik import Coord
class ServiceHandler(WMSBaseServiceHandler):
@ -98,7 +99,6 @@ class ServiceHandler(WMSBaseServiceHandler):
<Layer>
<Title>A Mapnik WMS Server</Title>
<Abstract>A Mapnik WMS Server</Abstract>
<SRS/>
</Layer>
</Capability>
</WMT_MS_Capabilities>
@ -107,10 +107,10 @@ class ServiceHandler(WMSBaseServiceHandler):
def __init__(self, conf, mapfactory, opsonlineresource):
self.conf = conf
self.mapfactory = mapfactory
if self.conf.has_option('service', 'epsg'):
self.crs = CRS('EPSG', self.conf.get('service', 'epsg'))
if self.conf.has_option('service', 'allowedepsgcodes'):
self.allowedepsgcodes = map(lambda code: 'epsg:%s' % code, self.conf.get('service', 'allowedepsgcodes').split(','))
else:
ServerConfigurationError('EPSG code not properly configured.')
raise ServerConfigurationError('Allowed EPSG codes not properly configured.')
capetree = ElementTree.fromstring(self.capabilitiesxmltemplate)
@ -122,22 +122,25 @@ class ServiceHandler(WMSBaseServiceHandler):
rootlayerelem = capetree.find('{http://www.opengis.net/wms}Capability/{http://www.opengis.net/wms}Layer')
rootlayersrs = rootlayerelem.find('{http://www.opengis.net/wms}SRS')
rootlayersrs.text = str(self.crs)
for epsgcode in self.allowedepsgcodes:
rootlayercrs = ElementTree.Element('SRS')
rootlayercrs.text = epsgcode.upper()
rootlayerelem.append(rootlayercrs)
for layer in self.mapfactory.layers.values():
layerproj = Projection(layer.srs)
layername = ElementTree.Element('Name')
layername.text = layer.name
env = layer.envelope()
llp = self.crs.inverse(env.minx, env.miny)
urp = self.crs.inverse(env.maxx, env.maxy)
llp = layerproj.inverse(Coord(env.minx, env.miny))
urp = layerproj.inverse(Coord(env.maxx, env.maxy))
latlonbb = ElementTree.Element('LatLonBoundingBox')
latlonbb.set('minx', str(llp.x))
latlonbb.set('miny', str(llp.y))
latlonbb.set('maxx', str(urp.x))
latlonbb.set('maxy', str(urp.y))
layerbbox = ElementTree.Element('BoundingBox')
layerbbox.set('SRS', str(self.crs))
layerbbox.set('SRS', layerproj.epsgstring())
layerbbox.set('minx', str(env.minx))
layerbbox.set('miny', str(env.miny))
layerbbox.set('maxx', str(env.maxx))
@ -173,8 +176,9 @@ class ServiceHandler(WMSBaseServiceHandler):
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')
if str(params['srs']) not in self.allowedepsgcodes:
raise OGCException('Unsupported SRS "%s" requested.' % str(params['srs']).upper(), 'InvalidSRS')
params['crs'] = params['srs']
return WMSBaseServiceHandler.GetMap(self, params)
class ExceptionHandler(BaseExceptionHandler):

View file

@ -21,9 +21,10 @@
from common import ParameterDefinition, Response, Version, ListFactory, \
ColorFactory, CRSFactory, CRS, WMSBaseServiceHandler, \
BaseExceptionHandler
BaseExceptionHandler, Projection
from exceptions import OGCException, ServerConfigurationError
from lxml import etree as ElementTree
from mapnik import Coord
class ServiceHandler(WMSBaseServiceHandler):
@ -104,7 +105,6 @@ class ServiceHandler(WMSBaseServiceHandler):
<Layer>
<Title>A Mapnik WMS Server</Title>
<Abstract>A Mapnik WMS Server</Abstract>
<CRS/>
</Layer>
</Capability>
</WMS_Capabilities>
@ -113,10 +113,10 @@ class ServiceHandler(WMSBaseServiceHandler):
def __init__(self, conf, mapfactory, opsonlineresource):
self.conf = conf
self.mapfactory = mapfactory
if self.conf.has_option('service', 'epsg'):
self.crs = CRS('EPSG', self.conf.get('service', 'epsg'))
if self.conf.has_option('service', 'allowedepsgcodes'):
self.allowedepsgcodes = map(lambda code: 'epsg:%s' % code, self.conf.get('service', 'allowedepsgcodes').split(','))
else:
raise ServerConfigurationError('EPSG code not properly configured.')
raise ServerConfigurationError('Allowed EPSG codes not properly configured.')
capetree = ElementTree.fromstring(self.capabilitiesxmltemplate)
@ -128,16 +128,19 @@ class ServiceHandler(WMSBaseServiceHandler):
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 epsgcode in self.allowedepsgcodes:
rootlayercrs = ElementTree.Element('CRS')
rootlayercrs.text = epsgcode.upper()
rootlayerelem.append(rootlayercrs)
for layer in self.mapfactory.layers.values():
layerproj = Projection(layer.srs)
layername = ElementTree.Element('Name')
layername.text = layer.name
env = layer.envelope()
layerexgbb = ElementTree.Element('EX_GeographicBoundingBox')
ll = self.crs.inverse(env.minx, env.miny)
ur = self.crs.inverse(env.maxx, env.maxy)
ll = layerproj.inverse(Coord(env.minx, env.miny))
ur = layerproj.inverse(Coord(env.maxx, env.maxy))
exgbb_wbl = ElementTree.Element('westBoundLongitude')
exgbb_wbl.text = str(ll.x)
layerexgbb.append(exgbb_wbl)
@ -151,7 +154,7 @@ class ServiceHandler(WMSBaseServiceHandler):
exgbb_nbl.text = str(ur.y)
layerexgbb.append(exgbb_nbl)
layerbbox = ElementTree.Element('BoundingBox')
layerbbox.set('CRS', str(self.crs))
layerbbox.set('CRS', layerproj.epsgstring())
layerbbox.set('minx', str(env.minx))
layerbbox.set('miny', str(env.miny))
layerbbox.set('maxx', str(env.maxx))
@ -189,8 +192,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')
if str(params['crs']) not in self.allowedepsgcodes:
raise OGCException('Unsupported CRS "%s" requested.' % str(params['crs']).upper(), 'InvalidCRS')
return WMSBaseServiceHandler.GetMap(self, params)
class ExceptionHandler(BaseExceptionHandler):

View file

@ -22,8 +22,8 @@ Features/Caveats
- JPEG/PNG output
- XML/INIMAGE/BLANK error handling
- Multiple named styles support
- No real layer metadata support yet
- No re-projection support
- Reprojection support
- Supported layer metadata: title, abstract
- Needs to be able to write to tempfile.gettempdir() (most likely "/tmp")
@ -41,22 +41,8 @@ Please properly install the following before proceeding further:
Installation
------------
- Make sure you compile or re-compile Mapnik after installing PROJ.4, if this
was not done already. Mapnik includes a Python API to the PROJ.4 library
used by the ogcserver. The PROJ_INCLUDES and PROJ_LIBS parameters can
be passed to mapnik's scons build script to help with this.
You can test the availability of the PROJ.4 bindings by trying:
$ python
Python 2.3.4 (#1, Feb 22 2005, 04:09:37)
[GCC 3.4.3 20041212 (Red Hat 3.4.3-9.EL4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from mapnik import Projection
registered datasource : raster
registered datasource : shape
registered datasource : postgis
>>>
- Make sure Mapnik was compiled and linked with PROJ.4 support. If this isn't
the case, recompile Mapnik and make sure it is.
- The executable "ogcserver" in utils/ogcserver will work for both CGI and
FastCGI operations. Where to place it will depend on your server's
@ -92,6 +78,7 @@ 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 import Layer, Style
class WMSFactory(BaseWMSFactory):
@ -101,7 +88,9 @@ class WMSFactory(BaseWMSFactory):
...
self.register_style('stylename', sty)
lyr = Layer(name='layername', title='Highways', abstract='Highways')
lyr = Layer('layername', '+init=epsg:4326')
lyr.title = 'Layer title'
lyr.abstract = 'Layer abstract'
...
self.register_layer(lyr, 'stylename')
self.finalize()
@ -111,11 +100,14 @@ 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.
- Layers MUST be named with the first parameter to the constructor.
- Layers MUST define an EPSG projection in the second parameter to the
constructor. This implies that the underlying data must be in an EPSG
projection already.
- style and layer names are meant for machine readability, not human. Keep
them short and simple, without spaces or special characters.
- For human readable info, pass title='' and abstract='' parameters to the
Layer() call.
- For human readable info, set the title and abstract properties on the layer
object.
- DO NOT register styles using layer.styles.append(), instead, provide style
information to the register_layer() call: