#!/usr/bin/env python # coding=utf-8 import urlparse import urllib from lxml import objectify try: from lxml import etree except: from xml.etree import ElementTree as etree import os,sys import tempfile import logging import ConfigParser import md5 import mapscript from string import Template from osgeo import osr from osgeo import ogr import OWSExceptions from owslib import crs as CRS import shutil class OWS: capabilities = None url = None requestUrl = None mapobj = None qstring = None owsns11 = "http://www.opengis.net/ows/1.1" owsns = "http://www.opengis.net/ows" cachedir = None mapfileName = "mapfile.map" config = None parsedUrl = None service = None mapfilename = None def __init__(self,url=None,qstring=None,configFile=None): self.requestUrl = url configFiles = [ os.path.join(os.path.dirname(__file__),"config.cfg") ] if sys.platform == "win32": pass # TODO Default conf. file on windows platform elif sys.platform == "linux2": configFiles.append("/etc/owsviewer.cfg") if configFile: configFiles.append(configFile) self.config = ConfigParser.ConfigParser() self.config.read(configFiles) logging.debug("Creating cachedir for %s in %s" % (self.url,self.config.get("Proxy4OWS","cachedir"))) logging.debug("%s initialized"%self.service) if url: self.url = url logging.debug("OWS.py::__init__()::url: '%s'" % url) if qstring: self.qstring = qstring self.__getCapabilities() def __getCapabilitiesFromFile(self,service,version): if os.path.exists(os.path.join(self.cachedir,"capabilities.xml")): logging.debug("OWS.py:: Using cached version of capabilities document") capafile = open(os.path.join(self.cachedir,"capabilities.xml")) return service(self.url,xml=capafile.read(),version=version) else: return None def __getCapabilities(self): if self.url: logging.debug("OWS.py::__getCapabilities::self.url: '%s'" % self.url) self.parsedUrl = urlparse.urlparse(self.url) self.__getCacheDir() if self.service == "WFS": from owslib.wfs import WebFeatureService # FIXME Make default version configurable try: # raise "2.0.0 not supported" # FIXME continue to 1.1.0 self.capabilities = self.__getCapabilitiesFromFile(WebFeatureService,"2.0.0") if self.capabilities == None and self.url: logging.debug("OWS.py:: Downloading new capabilities file for WFS 2.0.0") self.capabilities = WebFeatureService(url=self.url,version="2.0.0") except Exception,e: try: self.capabilities = self.__getCapabilitiesFromFile(WebFeatureService,"1.1.0") if self.capabilities == None and self.url: logging.debug("OWS.py:: Downloading new capabilities file for WFS 1.1.0") self.capabilities = WebFeatureService(url=self.url,version="1.1.0") except: self.capabilities = self.__getCapabilitiesFromFile(WebFeatureService,"1.0.0") if self.capabilities == None and self.url: logging.debug("OWS.py:: Downloading new capabilities file for WFS 1.0.0") self.capabilities = WebFeatureService(url=self.url,version="1.0.0") elif self.service == "WCS": from owslib.wcs import WebCoverageService self.capabilities = self.__getCapabilitiesFromFile(WebCoverageService,"1.0.0") if self.capabilities == None and self.url: logging.debug("OWS.py:: Downloading new capabilities file for WCS 1.0.0") self.capabilities = WebCoverageService(url=self.url,version="1.0.0") def getParams(self): params = urlparse.parse_qs(self.qstring) logging.debug(str(params)) if not "VERSION" in params.keys() and "version" in params.keys(): params["VERSION"] = params.pop("version") if not "LAYERS" in params.keys() and "layers" in params.keys(): params["LAYERS"] = params.pop("layers") if not "FORMAT" in params.keys() and "format" in params.keys(): params["FORMAT"] = params.pop("format") if not "STYLES" in params.keys() and "styles" in params.keys(): params["STYLES"] = params.pop("styles") if not "TIME" in params.keys() and "time" in params.keys(): params["TIME"] = params.pop("time") #else: # params["STYLES"] = [''] if not "TRANSPARENT" in params.keys() and "transparent" in params.keys(): params["TRANSPARENT"] = params.pop("transparent") return params def getOnlineResource(self,onlineresource,mapfilename=None): o = urlparse.urlparse(onlineresource) params = urlparse.parse_qs(o.query,keep_blank_values=True) params["owsUrl"] = self.url params["owsService"] = self.service params["map"] = self.getMapfileLocation(mapfilename) location = urlparse.urlunparse((o[0],o[1],o[2],o[3],urllib.urlencode(params,True),o[5])) logging.debug("Setting OnlineResource to %s"% location) return location def getMapfileLocation(self,mapfilename=None): # save the map if possible if mapfilename: mapfilename.replace("..","") # remove potential path change # mapfile must end with .map and cachedir must be at the # beginning of the mapfile name if mapfilename.endswith(".map") and \ mapfilename.find(self.cachedir) == 0: return mapfilename else: # do not save anything return # save to new location otherwice else: return os.path.join(self.cachedir,self.mapfileName) def __getCacheDir(self): dirname = os.path.join(self.config.get("Proxy4OWS","cachedir"), "%s-%s" % (self.service, md5.new(self.url).hexdigest())) self.cachedir = dirname # get existing cache dir if not os.path.isdir(dirname): os.mkdir(dirname) logging.debug("Cachedir %s created" % dirname) os.chmod(dirname, 0777) open(os.path.join(self.cachedir,"url.txt"),"w").write(self.url) else: logging.debug("Cachedir %s found" % dirname) return self.cachedir def dispatch(self): """Dispatch given request """ request = mapscript.OWSRequest() request.loadParams() mapobj = None self.request=request.getValueByName("REQUEST") # if no 'map' parameter in URL, create new mapfile if not request.getValueByName("map"): logging.debug("Creating new mapfile") mapobj = self.makeMap() else: # there is 'map' parameter in URL and the file exists, load it logging.debug("Using existing mapfile %s" % request.getValueByName("map")) if os.path.isfile(request.getValueByName("map")): mapobj = mapscript.mapObj(request.getValueByName("map")) # there is 'map' parameter in URL BUT the file does not exist: # create else: logging.debug("Mapfile not found: creating new mapfile") mapobj = self.makeMap(request.getValueByName("map")) # mapobj.getLayerByName(request.getValueByName("layers")).metadata.get("wfs_filter") logging.debug("Calling OWSDispatch") res = mapobj.OWSDispatch(request) if mapscript.MS_DONE == res: raise OWSExceptions.NoValidRequest("No valid OWS Request") elif mapscript.MS_FAILURE == res: pass #raise OWSExceptions.RequestFailed("Request failed") def getMapObj(self,mapfilename=None): if self.url is not None and self.capabilities is None: self.__getCapabilities() # nothing has changed in the capabilities document since last # request ? if os.path.exists(os.path.join(self.cachedir,"capabilities.xml")) and\ "_capabilities" in dir(self.capabilities): oldCapsFile = open(os.path.join(self.cachedir,"capabilities.xml")) oldCaps = oldCapsFile.read() oldCapsFile.close() # the capabilities document is up-to-date, load existing # mapfile newXml = etree.tostring(self.capabilities._capabilities) if md5.new(oldCaps).hexdigest() == md5.new(newXml).hexdigest(): newCapsFile = open(os.path.join(self.cachedir,"capabilities.xml"),"w") newCapsFile.write(newXml) mapfilename = self.getMapfileLocation() if os.path.exists(mapfilename): logging.debug("Capabilities unchanged, using existing mapfile %s" % self.getMapfileLocation()) return mapscript.mapObj(mapfilename) # finally # clear existing cached files logging.debug("Cached capabilities document is outdated, creating new one") shutil.rmtree(self.cachedir) # reate new one self.__getCacheDir() return self._createNewMapObj(mapfilename) def _createNewMapObj(self,mapfilename=None): mapobj = mapscript.mapObj() mapobj.setMetaData("wms_onlineresource",self.getOnlineResource(self.config.get("MapServer","onlineresource"),mapfilename)) logging.debug("Setting SRS to %s"%self.config.get("MapServer","srs")) mapobj.setMetaData("wms_srs",self.config.get("MapServer","srs")) mapobj.setProjection("init=epsg:4326") mapobj.setSize(500,500) mapobj.setExtent(-180,-90,90,180) mapobj.shapepath = self.cachedir mapobj.setMetaData("wms_encoding","utf-8") errfile = self.config.get("MapServer","errorfile") logging.debug("Setting ERRORFILE to %s"%errfile) if not os.path.exists(errfile) and errfile != "stderr": tmp = open(errfile,"w") tmp.close() # file if os.access(errfile, os.W_OK): mapobj.setConfigOption("MS_ERRORFILE",errfile) # stderr elif errfile == "stderr": mapobj.setConfigOption("MS_ERRORFILE",errfile) # no error file set else: logging.warning("Cannot set ERRORFILE to %s: %s " % (errfile,"Write access denided")) logging.debug("Setting IMAGEPATH to %s"%self.config.get("MapServer","imagepath")) mapobj.web.imagepath=self.config.get("MapServer","imagepath") mapobj.setMetaData("ows_enable_request","*") return mapobj def saveMapfile(self,mapobj,mapfilename): self.mapfilename = self.getMapfileLocation(mapfilename) if self.mapfilename: # save mapfile ONLY if GetCapabilities requested - it makes no # sense for other cases logging.info("Saving mapfile to %s" % self.mapfilename) mapobj.save(self.mapfilename) # cache capabilities document if "_capabilities" in dir(self.capabilities): logging.info("Saving service Capabilities to %s" % os.path.join(self.cachedir,"capabilities.xml")) open(os.path.join(self.cachedir,"capabilities.xml"),"w").write(etree.tostring(self.capabilities._capabilities)) else: logging.info("Mapfile NOT saved") return self.mapfilename def getLayerUrl(self): layerurl = self.url if self.url.find("?") > -1: if not self.url.endswith("?") and\ not self.url.endswith("&"): layerurl += "&" else: layerurl += "?" return layerurl def createLayerDefinitionFile(self, name, templatefile,time="",target=None): """ Depreacted to be removed """ layerurl = self.getLayerUrl() defFileName = None if target: defFileName = target else: defFileName = os.path.join(self.cachedir,'%s.%s'%(name,self.service.lower())) if time: time = ""+time+"" if not os.path.isfile(defFileName): open(defFileName,'w').write( Template(open(templatefile).read()).substitute( dict(url= layerurl, name=name,time=time,extras="&BAND=1,2,3"))) # FIXME always takes band 1,2,3 ^^ logging.debug("Created %s layer definition file" % defFileName) else: logging.debug("Using existing layer definition file %s" % defFileName) return defFileName def getLayerExtent(self,layer,crs=None): """Get extent of layer in form of minx, miny, maxx,maxy :returns: [minx, miny, maxx, maxy] """ bbox = None if layer.boundingBoxWGS84: dest = osr.SpatialReference() dest.ImportFromEPSG(crs.code) source = osr.SpatialReference() source.ImportFromEPSG(4326) bbox = [] # TODO rewrite this using ogr CoordinateTransformation # http://www.gdal.org/ogr/osr_tutorial.html # WELL: it does NOT seem to be THAT better geom = ogr.CreateGeometryFromWkt("""POINT(%s %s)""" % (layer.boundingBoxWGS84[0],layer.boundingBoxWGS84[1]),source) geom.TransformTo(dest) bbox.append(geom.GetX()) bbox.append(geom.GetY()) geom = ogr.CreateGeometryFromWkt("""POINT(%s %s)""" % (layer.boundingBoxWGS84[2],layer.boundingBoxWGS84[3]),source) geom.TransformTo(dest) bbox.append(geom.GetX()) bbox.append(geom.GetY()) logging.debug("Setting extent for layer <%s> to %s" %\ (layer.id,bbox)) return bbox def getService(configFile=None): qstring = os.environ["QUERY_STRING"] params = urlparse.parse_qs(qstring) urlparam = None serviceparam = None for p in params: if p.lower() == "owsurl": v = params[p] #del params[p] #params["owsUrl"] = v urlparam = v if p.lower() == "owsservice": v = params[p] #del params[p] #params["owsService"] = v serviceparam = v if urlparam: params["owsUrl"] = urlparam if serviceparam: params["owsService"] = serviceparam if "owsUrl" in params.keys() and\ "owsService" in params.keys(): owsUrl = urllib.unquote(params["owsUrl"][0]) if params["owsService"][0].lower() == "wfs": from wfs import WFS logging.debug("OWS.py::getService()::owsUrl: '%s'" % owsUrl) return WFS(owsUrl,qstring,configFile = configFile ) elif params["owsService"][0].lower() == "wcs": from wcs import WCS return WCS(owsUrl,qstring, configFile = configFile) elif params["owsService"][0].lower() == "wms": from wms import WMS return WMS(owsUrl,qstring, configFile = configFile) else: raise OWSExceptions.MissingParameterValue("""owsUrl or owsService""")