- 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:
parent
ce1353553c
commit
85f1d27a12
5 changed files with 58 additions and 51 deletions
|
@ -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
|
||||
|
|
|
@ -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()
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue