OWS.py 14 KB

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