OWS.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. #!/usr/bin/env python
  2. # coding=utf-8
  3. import urlparse
  4. import urllib
  5. from lxml import objectify
  6. from lxml import etree
  7. import os,sys
  8. import tempfile
  9. import logging
  10. import ConfigParser
  11. import md5
  12. import mapscript
  13. from string import Template
  14. from osgeo import osr
  15. from osgeo import ogr
  16. import OWSExceptions
  17. from owslib import crs as CRS
  18. import shutil
  19. from xml.etree import ElementTree
  20. class OWS:
  21. capabilities = None
  22. url = None
  23. requestUrl = None
  24. mapobj = None
  25. qstring = None
  26. owsns11 = "http://www.opengis.net/ows/1.1"
  27. owsns = "http://www.opengis.net/ows"
  28. cachedir = None
  29. mapfileName = "mapfile.map"
  30. config = None
  31. parsedUrl = None
  32. service = None
  33. mapfilename = None
  34. def __init__(self,url=None,qstring=None,configFile=None):
  35. self.requestUrl = url
  36. configFiles = [ os.path.join(os.path.dirname(__file__),"config.cfg") ]
  37. if sys.platform == "win32":
  38. pass # TODO Default conf. file on windows platform
  39. elif sys.platform == "linux2":
  40. configFiles.append("/etc/owsviewer.cfg")
  41. if configFile:
  42. configFiles.append(configFile)
  43. self.config = ConfigParser.ConfigParser()
  44. self.config.read(configFiles)
  45. logging.debug("Creating cachedir for %s in %s" % (self.url,self.config.get("Proxy4OWS","cachedir")))
  46. logging.debug("%s initialized"%self.service)
  47. if url:
  48. self.url = url
  49. logging.debug("OWS.py::__init__()::url: '%s'" % url)
  50. self.__getCapabilities()
  51. if qstring:
  52. self.qstring = qstring
  53. def __getCapabilities(self):
  54. self.__getCacheDir()
  55. logging.debug("OWS.py::__getCapabilities::self.url: '%s'" % self.url)
  56. self.parsedUrl = urlparse.urlparse(self.url)
  57. if self.service == "WFS":
  58. from owslib.wfs import WebFeatureService
  59. # FIXME Make default version configurable
  60. try:
  61. self.capabilities = WebFeatureService(url=self.url,version="1.0.0")
  62. except:
  63. self.capabilities = WebFeatureService(url=self.url,version="1.1.0")
  64. elif self.service == "WCS":
  65. from owslib.wcs import WebCoverageService
  66. self.capabilities = WebCoverageService(url=self.url,version="1.0.0")
  67. def getParams(self):
  68. params = urlparse.parse_qs(self.qstring)
  69. if not "VERSION" in params.keys() and "version" in params.keys():
  70. params["VERSION"] = params.pop("version")
  71. if not "LAYERS" in params.keys() and "layers" in params.keys():
  72. params["LAYERS"] = params.pop("layers")
  73. if not "FORMAT" in params.keys() and "format" in params.keys():
  74. params["FORMAT"] = params.pop("format")
  75. if not "STYLES" in params.keys() and "styles" in params.keys():
  76. params["STYLES"] = params.pop("styles")
  77. else:
  78. params["STYLES"] = ['']
  79. if not "TRANSPARENT" in params.keys() and "transparent" in params.keys():
  80. params["TRANSPARENT"] = params.pop("transparent")
  81. return params
  82. def getOnlineResource(self,onlineresource,mapfilename=None):
  83. o = urlparse.urlparse(onlineresource)
  84. params = urlparse.parse_qs(o.query,keep_blank_values=True)
  85. params["owsUrl"] = self.url
  86. params["owsService"] = self.service
  87. params["map"] = self.getMapfileLocation(mapfilename)
  88. location = urlparse.urlunparse((o[0],o[1],o[2],o[3],urllib.urlencode(params,True),o[5]))
  89. logging.debug("Setting OnlineResource to %s"% location)
  90. return location
  91. def getMapfileLocation(self,mapfilename=None):
  92. # save the map if possible
  93. if mapfilename:
  94. mapfilename.replace("..","") # remove potential path change
  95. # mapfile must end with .map and cachedir must be at the
  96. # beginning of the mapfile name
  97. if mapfilename.endswith(".map") and \
  98. mapfilename.find(self.cachedir) == 0:
  99. return mapfilename
  100. else:
  101. # do not save anything
  102. return
  103. # save to new location otherwice
  104. else:
  105. return os.path.join(self.cachedir,self.mapfileName)
  106. def __getCacheDir(self):
  107. dirname = os.path.join(self.config.get("Proxy4OWS","cachedir"),
  108. "%s-%s" % (self.service, md5.new(self.url).hexdigest()))
  109. self.cachedir = dirname
  110. # get existing cache dir
  111. if not os.path.isdir(dirname):
  112. os.mkdir(dirname)
  113. logging.debug("Cachedir %s created" % dirname)
  114. os.chmod(dirname, 0777)
  115. open(os.path.join(self.cachedir,"url.txt"),"w").write(self.url)
  116. else:
  117. logging.debug("Cachedir %s found" % dirname)
  118. return self.cachedir
  119. def dispatch(self):
  120. """Dispatch given request
  121. """
  122. request = mapscript.OWSRequest()
  123. request.loadParams()
  124. mapobj = None
  125. self.request=request.getValueByName("REQUEST")
  126. # if no 'map' parameter in URL, create new mapfile
  127. if not request.getValueByName("map"):
  128. logging.debug("Creating new mapfile")
  129. mapobj = self.makeMap()
  130. else:
  131. # there is 'map' parameter in URL and the file exists, load it
  132. logging.debug("Using existing mapfile %s" % request.getValueByName("map"))
  133. if os.path.isfile(request.getValueByName("map")):
  134. mapobj = mapscript.mapObj(request.getValueByName("map"))
  135. # there is 'map' parameter in URL BUT the file does not exist:
  136. # create
  137. else:
  138. mapobj = self.makeMap(request.getValueByName("map"))
  139. # WFS Filter encoding, if available
  140. if request.getValueByName("fes"):
  141. self.setFilter(mapobj,request)
  142. # mapobj.getLayerByName(request.getValueByName("layers")).metadata.get("wfs_filter")
  143. # here is still fine, but it fails on dispatch:
  144. res = mapobj.OWSDispatch(request)
  145. if mapscript.MS_DONE == res:
  146. raise OWSExceptions.NoValidRequest("No valid OWS Request")
  147. elif mapscript.MS_FAILURE == res:
  148. pass
  149. #raise OWSExceptions.RequestFailed("Request failed")
  150. def getMapObj(self,mapfilename=None):
  151. if self.url is not None and self.capabilities is None:
  152. self.__getCapabilities()
  153. # nothing has changed in the capabilities document since last
  154. # request ?
  155. if os.path.exists(os.path.join(self.cachedir,"capabilities.xml")) and\
  156. "_capabilities" in dir(self.capabilities):
  157. oldCapsFile = open(os.path.join(self.cachedir,"capabilities.xml"))
  158. oldCaps = oldCapsFile.read()
  159. oldCapsFile.close()
  160. # the capabilities document is up-to-date, load existing
  161. # mapfile
  162. newXml = ElementTree.tostring(self.capabilities._capabilities)
  163. if md5.new(oldCaps).hexdigest() == md5.new(newXml).hexdigest():
  164. newCapsFile = open(os.path.join(self.cachedir,"capabilities.xml"),"w")
  165. newCapsFile.write(newXml)
  166. mapfilename = self.getMapfileLocation()
  167. if os.path.exists(mapfilename):
  168. logging.debug("Capabilities unchanged, using existing mapfile %s" % self.getMapfileLocation())
  169. return mapscript.mapObj(mapfilename)
  170. # finally
  171. # clear existing cached files
  172. logging.debug("Cached capabilities document is outdated, creating new one")
  173. shutil.rmtree(self.cachedir)
  174. # reate new one
  175. self.__getCacheDir()
  176. return self._createNewMapObj(mapfilename)
  177. def _createNewMapObj(self,mapfilename=None):
  178. mapobj = mapscript.mapObj()
  179. mapobj.setMetaData("wms_onlineresource",self.getOnlineResource(self.config.get("MapServer","onlineresource"),mapfilename))
  180. logging.debug("Setting SRS to %s"%self.config.get("MapServer","srs"))
  181. mapobj.setMetaData("wms_srs",self.config.get("MapServer","srs"))
  182. mapobj.setProjection("init=epsg:4326")
  183. mapobj.setSize(500,500)
  184. mapobj.setExtent(-180,-90,90,180)
  185. mapobj.shapepath = self.cachedir
  186. mapobj.setMetaData("wms_encoding","utf-8")
  187. errfile = self.config.get("MapServer","errorfile")
  188. logging.debug("Setting ERRORFILE to %s"%errfile)
  189. if not os.path.exists(errfile) and errfile != "stderr":
  190. tmp = open(errfile,"w")
  191. tmp.close()
  192. # file
  193. if os.access(errfile, os.W_OK):
  194. mapobj.setConfigOption("MS_ERRORFILE",errfile)
  195. # stderr
  196. elif errfile == "stderr":
  197. mapobj.setConfigOption("MS_ERRORFILE",errfile)
  198. # no error file set
  199. else:
  200. logging.warning("Cannot set ERRORFILE to %s: %s " % (errfile,"Write access denided"))
  201. logging.debug("Setting IMAGEPATH to %s"%self.config.get("MapServer","imagepath"))
  202. mapobj.web.imagepath=self.config.get("MapServer","imagepath")
  203. mapobj.setMetaData("ows_enable_request","*")
  204. return mapobj
  205. def saveMapfile(self,mapobj,mapfilename):
  206. self.mapfilename = self.getMapfileLocation(mapfilename)
  207. if self.mapfilename:
  208. # save mapfile ONLY if GetCapabilities requested - it makes no
  209. # sense for other cases
  210. logging.info("Saving mapfile to %s" % self.mapfilename)
  211. mapobj.save(self.mapfilename)
  212. # cache capabilities document
  213. if "_capabilities" in dir(self.capabilities):
  214. logging.info("Saving service Capabilities to %s" % os.path.join(self.cachedir,"capabilities.xml"))
  215. open(os.path.join(self.cachedir,"capabilities.xml"),"w").write(ElementTree.tostring(self.capabilities._capabilities))
  216. else:
  217. logging.info("Mapfile NOT saved")
  218. return self.mapfilename
  219. def getLayerUrl(self):
  220. layerurl = self.url
  221. if self.url.find("?") > -1:
  222. if not self.url.endswith("?") and\
  223. not self.url.endswith("&"):
  224. layerurl += "&"
  225. else:
  226. layerurl += "?"
  227. return layerurl
  228. def createLayerDefinitionFile(self, name,
  229. templatefile,time="",target=None):
  230. layerurl = self.getLayerUrl()
  231. defFileName = None
  232. if target:
  233. defFileName = target
  234. else:
  235. defFileName = os.path.join(self.cachedir,'%s.%s'%(name,self.service.lower()))
  236. if time:
  237. time = "<DefaultTime>"+time+"</DefaultTime>"
  238. if not os.path.isfile(defFileName):
  239. open(defFileName,'w').write(
  240. Template(open(templatefile).read()).substitute(
  241. dict(url= layerurl,
  242. name=name,time=time,extras="&BAND=1,2,3")))
  243. # FIXME always takes band 1,2,3 ^^
  244. logging.debug("Created %s layer definition file" % defFileName)
  245. else:
  246. logging.debug("Using existing layer definition file %s" % defFileName)
  247. return defFileName
  248. def getLayerExtent(self,layer,crs=None):
  249. """Get extent of layer in form of minx, miny, maxx,maxy
  250. :returns: [minx, miny, maxx, maxy]
  251. """
  252. bbox = None
  253. if layer.boundingBoxWGS84:
  254. dest = osr.SpatialReference()
  255. dest.ImportFromEPSG(crs.code)
  256. source = osr.SpatialReference()
  257. source.ImportFromEPSG(4326)
  258. bbox = []
  259. # TODO rewrite this using ogr CoordinateTransformation
  260. # http://www.gdal.org/ogr/osr_tutorial.html
  261. # WELL: it does NOT seem to be THAT better
  262. geom = ogr.CreateGeometryFromWkt("""POINT(%s %s)""" % (layer.boundingBoxWGS84[0],layer.boundingBoxWGS84[1]),source)
  263. geom.TransformTo(dest)
  264. bbox.append(geom.GetX())
  265. bbox.append(geom.GetY())
  266. geom = ogr.CreateGeometryFromWkt("""POINT(%s %s)""" % (layer.boundingBoxWGS84[2],layer.boundingBoxWGS84[3]),source)
  267. geom.TransformTo(dest)
  268. bbox.append(geom.GetX())
  269. bbox.append(geom.GetY())
  270. logging.debug("Setting extent for layer <%s> to %s" %\
  271. (layer.id,bbox))
  272. return bbox
  273. def setFilter(self,mapobj,request):
  274. """ Set WFS filter encoding
  275. """
  276. # get the layer
  277. layerobj = mapobj.getLayerByName(request.getValueByName("layers"))
  278. # get the filter
  279. fes = request.getValueByName("fes")
  280. logging.debug("FES received from HSLayers: %s" % fes)
  281. # cut off the opening and closing <Filter> tag
  282. # - this is needed for mapserver
  283. root = etree.XML(fes)
  284. msFilter = ""
  285. for child in root:
  286. msFilter += etree.tostring(child)
  287. logging.debug("Setting the filter %s" % msFilter)
  288. # set the filter
  289. layerobj.setMetaData("wfs_filter",msFilter)
  290. # save the mapfile - debugging
  291. mapobj.save("mapfile.fes")
  292. def getService(configFile=None):
  293. qstring = os.environ["QUERY_STRING"]
  294. params = urlparse.parse_qs(qstring)
  295. for p in params:
  296. if p.lower() == "owsurl":
  297. v = params[p]
  298. del params[p]
  299. params["owsUrl"] = v
  300. if p.lower() == "owsservice":
  301. v = params[p]
  302. del params[p]
  303. params["owsService"] = v
  304. if "owsUrl" in params.keys() and\
  305. "owsService" in params.keys():
  306. owsUrl = urllib.unquote(params["owsUrl"][0])
  307. if params["owsService"][0].lower() == "wfs":
  308. from wfs import WFS
  309. logging.debug("OWS.py::getService()::owsUrl: '%s'" % owsUrl)
  310. return WFS(owsUrl,qstring,configFile = configFile )
  311. elif params["owsService"][0].lower() == "wcs":
  312. from wcs import WCS
  313. return WCS(owsUrl,qstring, configFile = configFile)
  314. elif params["owsService"][0].lower() == "wms":
  315. from wms import WMS
  316. return WMS(owsUrl,qstring, configFile = configFile)
  317. else:
  318. raise OWSExceptions.MissingParameterValue("""owsUrl or owsService""")