Saturday, February 23, 2008

Python Mapscript Tricks

I mentioned previously a site which uses google maps with a WMS. The problem with using points (or labels) in a tiled application such as google maps, or OpenLayers is that each tile can only draw its own contents. So if you draw a point with a radius of 10 pixels whose center is 2 px away from the edge of the tile, then 8px of the entire 20px will be chopped. By default, those lost 8 pixels will not be drawn in the adjacent tile because the center of the point does not fall in that tile. so that gives something that looks like this for 2 adjacent tiles:



It's hard to even tell that those tiles belong together! Using some python mapscript and PIL, it's pretty simple to make those look like this( except I dont know how to tell blogger not to add the spacin...) :



That's actually the same WMS request(s), just changing the call from a simple WMS CGI script that calls mapserver to a WSGI script that uses python mapscript and PIL.
The script:
1. takes the current bounding box, width and height, and expands them all proportionally.
2. has mapscript draw the expanded image which is
3. saved into a stringIO object which is
4. sent that to PIL to be cropped down to size and extent.
5. returned to the browser.


#!/usr/bin/python

import mapscript
from cgi import parse_qsl
import os
import Image
from cStringIO import StringIO

BUFFER_PCT = 0.1
MAPFILE = 'soil.map'

########################################################
MAPFILE = os.path.join(os.path.dirname(__file__), MAPFILE)

def application(environ, start_response):
winput = dict(parse_qsl(environ['QUERY_STRING']))
format = winput.get('FORMAT', 'image/png')
start_response('200 OK', [('Content-Type', format)])
extension = format[format.find('/') + 1:]


wms = mapscript.mapObj(MAPFILE)
req = mapscript.OWSRequest()

for k,v in winput.items():
req.setParameter(k, v)

buffer = BUFFER_PCT/2

bbox = map(float, winput['BBOX'].split(","))
rangex = bbox[2] - bbox[0]
rangey = bbox[3] - bbox[1]
w = int(winput['WIDTH'])
h = int(winput['HEIGHT'])
xdelta = int(round(w * buffer)) # e.g add 13px on each side
ydelta = int(round(h * buffer)) # for 256x256 image

bbox[0] -= rangex * buffer # extend the
bbox[1] -= rangey * buffer # bbox in
bbox[2] += rangex * buffer # all
bbox[3] += rangey * buffer # directions

# http://trac.osgeo.org/mapserver/ticket/2299
req.setParameter('WIDTH', str(w + 2 * xdelta)) # and adjust the
req.setParameter('HEIGHT', str(h + 2 * ydelta)) # h, w by the same
req.setParameter('BBOX', ",".join(map(str,bbox))) # amount
req.setParameter("STYLES", "")
req.setParameter("REQUEST", "GetMap")

# PIL doesnt like interlace.
wms.outputformat.setOption('FORMATOPTIONS', 'INTERLACE=OFF')
wms.loadOWSParameters(req)

im = Image.open(StringIO(wms.draw().getBytes()))
if im is None: return ['']

# crop the image back to the requested w, h
im = im.crop((xdelta, ydelta, w + xdelta, h + ydelta))

buffer = StringIO()
im.save(buffer, extension)
buffer.seek(0)
data = buffer.read()
return [ data ]


That's pretty simple, but pretty cool. And it actually runs slightly faster than the CGI version. It runs even more quickly if "wms = mapscript.mapObj(MAPFILE)" is moved into the global scope, so the mapfile doesnt have to be re-parsed each time, but I didnt test enough to make sure that's ok.

Thursday, February 21, 2008

Open Source GIS

Just saw this linked from Sean's post. The quotes in there are absurd. But reminded me there's an important point in favor of OSS that I haven't seen.

You can still get help directly via the email list or IRC from F Warmerdam, it's primary author. Likewise for the developers of Mapserver and OpenLayers and PostGIS. I wonder if the lead developers of ESRI products spend their off-time perusing the forum's or mailing lists and answering questions? ( I don't know, they may. But I suspect not. )
There's something to be said for enjoying what you do. And I think that's very true in the case of those in the open-source community. Happy coders make better software. Any programmer that denies that will leave me flabbergasted.

I just don't understand how I could be effective only clicking the menus that were provided to me if I chose a black-box solution. But, call me crazy, I like linux.

Also, in these parts, if you lock your single-speed bike to a wooden chair, it'll get stolen regardless of the cost of the lock or the weight of the bike.