#!/usr/bin/python import sys,os import cgi import subprocess from subprocess import PIPE,Popen import socket import urllib2,urllib import tempfile import re from PIL import Image, ImageDraw, ImageFont faked_useragents ={ "Mozilla_unix_64": "Mozilla/5.0 (X11; U; Linux x86_64; de; rv:1.9.0.10) Gecko/2009042805 a Firefox 2.0.0.12 a Unix", "Mozilla_windows_xp": "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.0.10) Gecko/2009042316 Firefox/3.0.10 (.NET CLR 3.5.30729)", "IExplore_windows_xp": "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)" } # we need some default values # default_server: # key[0]: wms connect string # key[1]: projection system # key[2]: WMS/WEB --> are we querriing a webserver or an wms server # key[3]: dictonary with an cropbox # key[4]: dictonary with mapping from easy layernames -> list [real_layer_name,style_for layer] # key[5]: which user_agent should be faked # key[6]: which image format should be requested default_server = { # whole germany - sometimes black "DOPV": ["http://gdz-dop.bkg.bund.de/arcgis/services/DOP/ImageServer/WMSServer?", "EPSG:25832", "WMS", {"top":0,"right":0,"bottom":0,"left":0}, { "DOPC":["0","default"], }, faked_useragents["Mozilla_unix_64"], "image/jpeg"], # nice orthos, but not every where these are colored "TH": ["http://www.geoproxy.geoportal-th.de/geoproxy/services?", "EPSG:31468", "WMS", {"top":0,"right":0,"bottom":20,"left":0}, { "DOPC":["dop20c","default"], "DOPSW":["dop20sw","default"], "DOP2C":["dop2c","default"], "DOP2SW":["dop2sw","default"], "GEMEINDEGRENZEN250":["GGK250","default"], "GEMEINDEGRENZEN":["gemeindegrenzen","default"], "GEMARKUNGSGRENZEN":["gemarkungsgrenten","default"], "FLURGRENZEN":["flurgrenzen","default"] }, faked_useragents["Mozilla_unix_64"], "image/jpeg"], # sometimes the server don't delivers any answer, even if a tcp connection is # up and running "SN": ["http://atlas.egov.sachsen.de/geobak/servlet/gtEntryPoint;?", "EPSG:31468", "WMS", {"top":0,"right":0,"bottom":0,"left":0}, { "DOPC":["layer_3533",""], "UEBERSICHTSKARTE":["map_602",""], "STRASSEN":["layer_4419",""], "TOP":["layer_3866",""] }, faked_useragents["Mozilla_unix_64"], "image/jpeg"], # when seeing these beach orthos ... ;-) "MV": ["http://www.gaia-mv.de/dienste/DOP?", "EPSG:31468", "WMS", {"top":5,"right":0,"bottom":0,"left":0}, { "DOPC":["DOP",""] }, faked_useragents["Mozilla_unix_64"], "image/jpeg"], # yes we take the orthophotos from the web :-) "BY": ["http://www.geodaten.bayern.de/dyn/view/img", "EPSG:31468", "WEB", {"top":5,"right":0,"bottom":20,"left":0}, { "DOPC":["dop",""], "TK":["tk",""] }, faked_useragents["Mozilla_unix_64"], "image/jpeg"], # oh dear, what a lousy webserver to query. the java version of theire client is also not working # until further explorations this server is commented out "SA": ["http://www.lvermgeo.sachsen-anhalt.de//de/geoservice/viewer/htmlviewer/htmlviewer.htm?powerProxyRequestURL=/navigator/basicviewer.jsp?", "EPSG:31468", "WEB", {"top":0,"right":0,"bottom":0,"left":0}, { "DOPC":["dop",""] }, faked_useragents["Mozilla_unix_64"], "image/jpeg"], # when the server is up, it works well - but delivers only 40cm resolution "HE": ["http://thunder2.hessen.de/cgi-bin/lika-services/de-viewer/access/ogc-free-images.plx?", "EPSG:31467", "WMS", {"top":0,"right":0,"bottom":0,"left":0}, { "DOPC":["DOP04F1","default"] }, faked_useragents["Mozilla_unix_64"], "image/jpeg"], # give me some working epsg-definition in /usr/share/proj/epsg and # the whole thing will be good "BB": ["http://isk.geobasis-bb.de/ows/dop20c.php?", "EPSG:325833", "WMS", {"top":0,"right":0,"bottom":0,"left":0}, { "DOPC":["18000",""] }, faked_useragents["Mozilla_unix_64"], "image/png"], # only here wile 'cause i checked the terrible 2m resolution :-D "BY2": ["http://www.geodaten.bayern.de/ogc/getogc.cgi?", "EPSG:4326", "WMS", {"top":0,"right":0,"bottom":0,"left":0}, { "DOPC":["DOP",""] }, faked_useragents["Mozilla_unix_64"], "image/jpeg"], # not implemented yet "BW": ["http://rips-uis.lubw.baden-wuerttemberg.de/ims/cgi?ServiceName=badekarte&ClientVersion=4.0&Form=True&Encode=False", "EPSG:31467", "WEB", {"top":0,"right":0,"bottom":0,"left":0}, { "DOPBW":["dopbw",""] }, faked_useragents["Mozilla_unix_64"], "image/gif"] } default_wms_values = { "SERVICE": "WMS", "VERSION": "1.1.1", "REQUEST": "GetMap", "BGCOLOR": "0xFFFFFF", "TRANSPARENT": "TRUE", "STYLES": "default", "EXCEPTIONS": "application/vnd.ogc.se_inimage" } default_srs = "EPSG:4326" default_format ="image/png" default_precision = 8 default_overlap = 4 default_crop = True default_fake_useragent = True default_timeout = 30 default_errorfont_name = "/usr/share/fonts/truetype/freefont/FreeMono.ttf" default_errorfont_size = 18 # initialize variables server = None #Server to querry for map wms = None #connect string for the remote wms bbox = None #bbox paramet in llx lly urx ury as string layers = None #layers to request --> default_server[x][4] styles = None #styles for the layers precision = None #precision in fractal when passing arguments srs_in = None #input spatial system srs_out = None #output spatial system width = None #width height = None #height overlap = None #amount of enlargement the map in % dpsx = 0 #x-enlargement in srs_in dpsy = 0 #y-enlargement in srs_in grid_i = [] #input grid in srs_in grid_o = [] #output grid in srs_in grid_ot = [] #outpur grid in srs_out crop = None #should the cropbox be recognized cropbox = [] #cropbbox for cutting the image fake_useragent = None #should the user-agent string be faked timeout = None #timeout in seconds we are waiting for an server response format_in = None #which image format should be delivered format_out = None #which format we are requesting # print the error strings ############# def printerror (errorstring): if (width is None) or (height is None): print "Content-type: text/html" print "" print "WMS-Errorfile" print errorstring print "" else: print "Content-type: image/jpeg\n" img = Image.new("RGB",(width,height)) if os.path.isfile(default_errorfont_name): font = ImageFont.truetype(default_errorfont_name,default_errorfont_size) else: font = ImageFont.load_default() draw = ImageDraw.Draw(img) for i,line in enumerate(errorstring.splitlines()): draw.text((5,15*i+5),line,(200,200,200),font) img.save(sys.stdout,"JPEG") sys.exit(2) # build an function for handeling an url request ############# def fetchurl (req): if server in ["DOPV"]: #WMS Server from bund need those special headers req.add_header('Accept','text/*, image/png, image/gif, image/jpeg, *; q=.2, */*; q=.3') req.add_header('gdz-group','1028196') if fake_useragent: req.add_header('User-Agent',default_server[server][5]) try: answer = urllib2.urlopen(req) return answer except urllib2.URLError, e: printerror("Problem occured contacting outging web server %s\n%s" %(e,req.get_full_url())) except socket.error: printerror("The Server is not reachable") # transformes point ix,iy form srs_i to srs_o with precsision prec ############# def transform (prec,srs_i, srs_o, ix,iy): param = '-s_srs %s -t_srs %s << START\n%.*f %.*f\nSTART' % (srs_i,srs_o,prec,ix,prec,iy) return subprocess.Popen(['gdaltransform '+param],stdout=PIPE,shell=True).communicate()[0].split(' ')[0:2] # search for necessary tools ############# def tools_okay(): tools = ["gdaltransform","gdal_translate","gdalwarp"] missing = "" for tool in tools: try: subprocess.check_call(["which",tool]) except CalledProcessError: missing+="%s\n"% (tool) if missing != "": printerror("The following tools could not be found,\nor are not accessible from the cgi-script\n"+missing) ############ # parse the http GET parameters ########### myparams = {} formdata = cgi.FieldStorage() for i in formdata: myparams[i.upper()]=formdata.getlist(i) for k in myparams: if k == "BBOX": bbox = myparams[k][0] if k == "SERVER": server = myparams[k][0] elif k == "LAYERS": layers = myparams[k][0] elif k == "STYLES": styles = myparams[k][0] elif k == "SRS": srs_in = myparams[k][0] elif k == "HEIGHT": height = myparams[k][0] elif k == "WIDTH": width = myparams[k][0] elif k == "CROP": crop = myparams[k][0] elif k in default_wms_values: default_wms_values[k] = myparams[k][0] elif k =="FAKEUSERAGENT": fake_useragent = myparams[k][0] elif k =="TIMEOUT": timeout = myparams[k][0] elif k =="FORMAT": format_in = myparams[k][0] # check the parameters ################## if width is None: #check width printerror("There must be an width parameter") else: try: width = int(width) except ValueError: printerror("WIDTH must be an integer value") if height is None: #check height printerror("There must be an height parameter") else: try: height = int(height) except ValueError: printerror("HEIGHT must be an integer value") if bbox is None: #check bbox printerror("There must be an bbox field parameter") sys.exit(2) else: try: for i in bbox.split(','): grid_i.append(float(i)) except ValueError: printerror("There must be float values in bbox") if len(grid_i) != 4: #check iv ve have exact 4 values in bbox printerror("There must be 4 values in bbox") if server is None: #check for server from default_server printerror("There must be an SERVER parameter") else: server = server.upper() if server not in default_server: errorstr = "SERVER %s is unknow to the script.\n" % (server) errorstr += "Possible Values for Server are:\n" for s in default_server: errorstr += "%s\n" % (s) printerror(errorstr) else: #fill some variables with values form default_server wms = default_server[server][0] srs_out = default_server[server][1] format_out = default_server[server][6].split('/')[1] if layers is None: #check layer printerror("There must be an LAYERS parameter") else: reallayers = "" for l in layers.split(','): if l.upper() not in default_server[server][4]: errorstr = "LAYER '%s' is not defined\n" % (l) errorstr += "Possible Values for LAYERS are:\n" for l in default_server[server][4]: errorstr += "%s\n" %l printerror(errorstr) else: reallayers += default_server[server][4][l.upper()][0] givenlayers = layers #need this copy later for checking styles layers = reallayers if styles is None: #check styles realstyles = '' for l in givenlayers.upper().split(','): realstyles += default_server[server][4][l][1] styles = realstyles else: if len(styles.split(',')) != len(layers.split(',')): printerror("The number of layers and styles in your request don't mach") if precision is None: #check precision precision = default_precision if srs_in is None: #cehck srs srs_in = default_srs if width is None: #check width printerror("There must be an width parameter") else: try: width = int(width) except ValueError: printerror("WIDTH must be an integer value") if height is None: #check height printerror("There must be an height parameter") else: try: height = int(height) except ValueError: printerror("HEIGHT must be an integer value") if overlap is None: #check overlap overlap = default_overlap if crop is None: #check crop crop = default_crop else: if crop.upper() == "TRUE": crop = True elif crop.upper() == "FALSE": crop = False else: crop = default_crop if fake_useragent is None: #check if we should fake the user agent fake_useragent = default_fake_useragent else: if fake_useragent.upper() == "TRUE": fake_useragent = True elif fake_useragent.upper() == "FALSE": fake_useragent = False else: printerror("Value for FAKEUSERAGENT can only be True/False") if timeout is None: #check and set the timeout for socket operation timeout = default_timeout else: try: timeout = int(timeout) except ValueError: printerror("Vale for TIMEOUT can only be an integer") socket.setdefaulttimeout(timeout) if format_in is None: # check input graphics format format_in = default_format.split('/')[1] else: if re.match("image/.+",format_in) is not None: format_in = format_in.split('/')[1] print srs_in print srs_out # now let's do the real work ;-) ########################################################################################################### # compute the enlargements for the 2 directions psx = (grid_i[2]-grid_i[0])/width psy = (grid_i[3]-grid_i[1])/height # compute cropbox values for enlargement the aera and later cutting if crop is True: values = default_server[server][3] cropbox = [psx*values["left"],psy*values["bottom"],psx*values["right"],psy*values["top"]] else: cropbox = [0,0,0,0] # compute the overlap values dpsx = (grid_i[2]-grid_i[0])/100*overlap dpsy = (grid_i[3]-grid_i[1])/100*overlap # compute the enlargment aera in original srs grid_o = [grid_i[0]-dpsx-cropbox[0],grid_i[1]-dpsy-cropbox[1],grid_i[2]+dpsx+cropbox[2],grid_i[3]+dpsy+cropbox[3]] # transform enlarged aerea into output srs grid_ot = transform(precision,srs_in,srs_out,grid_o[0],grid_o[1])+transform(precision,srs_in,srs_out,grid_o[2],grid_o[3]) #build the url for requesting the image if default_server[server][2] == "WMS": add2url = "" for key in default_wms_values: add2url+="&%s=%s" % (key,default_wms_values[key]) add2url+="&FORMAT=image/%s&LAYERS=%s&BBOX=%s&SRS=%s&WIDTH=%i&HEIGHT=%i" % (format_out,layers,','.join(map(str,grid_ot)),srs_out,width,height) # get the image request = urllib2.Request(url=str(wms+add2url)) mapimage = fetchurl(request) # we need some special handling if we are getting the image from the web else: if server in ["BY"]: request = urllib2.Request(url=str(wms)) #bavarian website uses only integer values, so we do round grid_otr = [float(x) for x in grid_ot] grid_otr = [int(grid_otr[0]),int(grid_otr[1]),int(grid_otr[2]+1),int(grid_otr[3]+1)] #add the request parameters 2 the post data request.add_data("url=%s_%s_%s_%s_%s" % (layers,','.join(map(str,grid_ot)),width,height,format_out)) picturepath = fetchurl(request) #get the picture request2 = urllib2.Request(url="%s://%s%s" % (request.get_type(),request.get_host(),picturepath.read())) mapimage = fetchurl(request2) else: errorwrite("Request of these webserver\n is still not implemented! Sorry") # go for image processing (get, crop, warp, translate etc. ######################################################################################################## wmsinput = tempfile.NamedTemporaryFile(mode='w+b',suffix='.%s.tmp'%(format_out),prefix='wmsviewer-') wmsinput.write(mapimage.read()) wmsinput.flush() wmsinput.seek(0) if wmsinput.read(1) == "": printerror("The outgoing server did not provide any data within time") #create some temporary file for image translation gdaltrans1 = tempfile.NamedTemporaryFile(mode='w+b',suffix='.tif.tmp',prefix='wmsviewer-') gdalwarp = tempfile.NamedTemporaryFile(mode='w+b',suffix='.tif.tmp',prefix='wmsviewer-') gdaltrans2 = tempfile.NamedTemporaryFile(mode='w+b',suffix='.%s.tmp'%(format_in),prefix='wmsviewer-') #build a georeferenced image paramgdaltrans1 = " -a_srs %s -a_ullr %s %s %s %s %s %s" % (srs_out,grid_ot[0],grid_ot[3],grid_ot[2],grid_ot[1],wmsinput.name,gdaltrans1.name) try: returncode = subprocess.call('gdal_translate'+paramgdaltrans1,shell=True,stdout=PIPE) if returncode != 0: printerror("Building the georeferenced image failed with returncode :%i"% (returncode)) except OSError,e: errorstr = "Building the georeferenced image gailed\n" errorstr += "Error:%s\n" errorstr += "process: gdal_translate\n" errorstr += "args:%s\n" % (e,paramgdaltrans1) printerror (errorstr) gdaltrans1.flush() #translate to srs_in paramgdalwarp = " -q -s_srs %s -t_srs %s %s %s" % (srs_out,srs_in,gdaltrans1.name,gdalwarp.name) subprocess.call('gdalwarp'+paramgdalwarp,shell=True,stdout=PIPE) gdalwarp.flush() #crop and translate to output format paramgdaltrans2 =" -quiet -outsize %i %i -of %s -projwin %f %f %f %f %s %s" % (width,height,format_in,grid_i[0],grid_i[3],grid_i[2],grid_i[1],gdalwarp.name,gdaltrans2.name) proc = subprocess.call('gdal_translate'+paramgdaltrans2,shell=True,stdout=PIPE) gdaltrans2.flush() gdaltrans2.seek(0) #deliver the image to the client print "Content-Type: image/%s\n" % (format_in) print gdaltrans2.read() #do some cleanup ;-) if os.path.isfile("%s.aux.xml"%(gdaltrans2.name)): os.unlink("%s.aux.xml"%(gdaltrans2.name)) #python could do this himself, but better safe, than sorry gdaltrans2.close() gdalwarp.close() gdaltrans1.close() wmsinput.close()