__init__.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. #!/usr/bin/env python
  2. # coding=utf-8
  3. from OWS import OWS
  4. import mapscript
  5. import cgi
  6. from lxml import objectify
  7. import urllib
  8. import urlparse
  9. import logging
  10. from osgeo import ogr,gdal,osr
  11. from owslib.crs import Crs
  12. import os,sys
  13. import re
  14. import shutil
  15. import tempfile
  16. class WFS(OWS):
  17. service = "WFS"
  18. wfsns = "http://www.opengis.net/wfs"
  19. layerDefFile = None
  20. lyrobj = None
  21. wfs = None
  22. def __init__(self,url=None,qstring=None,configFile=None):
  23. OWS.__init__(self,url,qstring,configFile)
  24. def dispatch(self):
  25. """Dispatch given request
  26. """
  27. request = mapscript.OWSRequest()
  28. request.loadParams()
  29. typename = request.getValueByName("layers")
  30. if typename:
  31. layer = self.capabilities.contents[typename]
  32. self.request=request.getValueByName("REQUEST")
  33. # if no 'map' parameter in URL, create new mapfile
  34. if not request.getValueByName("map"):
  35. logging.debug("Creating new mapfile")
  36. self.mapobj = self.makeMap()
  37. else:
  38. # there is 'map' parameter in URL and the file exists, load it
  39. logging.debug("Using existing mapfile %s" % request.getValueByName("map"))
  40. if os.path.isfile(request.getValueByName("map")):
  41. self.mapobj = mapscript.mapObj(request.getValueByName("map"))
  42. # there is 'map' parameter in URL BUT the file does not exist:
  43. # create
  44. else:
  45. self.mapobj = self.makeMap(request.getValueByName("map"))
  46. if self.mapobj:
  47. self.mapfilename = request.getValueByName("map")
  48. # download data
  49. if self.request.upper() in ["GETMAP", "GETFEATUREINFO"]:
  50. dataFile = self.getData(request,typename,layer)
  51. if not ogr.Open(dataFile):
  52. dataFile = self.getData(request,typename,layer)
  53. if dataFile:
  54. #layer.data = dataFile.name
  55. pass
  56. mapscript.msIO_installStdoutToBuffer()
  57. res = self.mapobj.OWSDispatch(request)
  58. if mapscript.MS_DONE == res:
  59. raise OWSExceptions.NoValidRequest("No valid OWS Request")
  60. elif mapscript.MS_FAILURE == res:
  61. pass
  62. #raise OWSExceptions.RequestFailed("Request failed")
  63. print "Content-type: %s\n" % (mapscript.msIO_stripStdoutBufferContentType())
  64. # HACK HACK HACK
  65. # Fix missing namespace encoding for ArcIMS and similar layers
  66. if self.request.upper() == "GETFEATUREINFO" and\
  67. typename.find(":") > -1:
  68. print self.__fixNamespace(typename)
  69. else:
  70. print mapscript.msIO_getStdoutBufferBytes()
  71. def getData(self,request,typename,layer):
  72. """Download data from WFS server and store them to local harddrive
  73. """
  74. fes = request.getValueByName("fes")
  75. bbox = request.getValueByName("bbox").split(",")
  76. featureid = request.getValueByName("featureid")
  77. featureversion = request.getValueByName("featureversion")
  78. datadir = os.path.join(self.cachedir,"cache")
  79. layerobj = self.mapobj.getLayerByName(typename)
  80. crs = self.__getLayerCrs(layer.crsOptions)
  81. version = request.getValueByName("version")
  82. # get propper bbox for the WFS request
  83. if version == "1.3.0":
  84. requestCrs = request.getValueByName("crs")
  85. else:
  86. requestCrs = request.getValueByName("srs")
  87. requestCrs = Crs(requestCrs)
  88. bbox = self.__adjustBBox(bbox,requestCrs, crs,version)
  89. # do not take anything behind max extent
  90. #bbox[0] = layerobj.extent.minx if layerobj.extent.minx >= bbox[0] else bbox[0]
  91. #bbox[1] = layerobj.extent.miny if layerobj.extent.miny >= bbox[1] else bbox[1]
  92. #bbox[2] = layerobj.extent.maxx if layerobj.extent.maxx <= bbox[2] else bbox[2]
  93. #bbox[3] = layerobj.extent.maxy if layerobj.extent.maxy <= bbox[3] else bbox[3]
  94. bbox = map(lambda x: round(float(x),4), bbox)
  95. # clear dir
  96. outfn = None
  97. if os.path.isdir(datadir):
  98. if os.path.isfile(os.path.join(datadir,"%s.gml"%typename)):
  99. # find out bbox of the data
  100. bboxfile = os.path.join(datadir,"%s.bbox"%typename)
  101. filterfile = os.path.join(datadir,"%s.filter"%typename)
  102. if os.path.isfile(bboxfile) and not os.path.isfile(filterfile)\
  103. and not fes:
  104. # convert "string" to [floats] as [minx,miny,maxx,maxy]
  105. storedbbox = map(lambda x: round(float(x),4), open(bboxfile).read().split(","))
  106. if (storedbbox[0] <= bbox[0] and\
  107. storedbbox[1] <= bbox[1] and \
  108. storedbbox[2] >= bbox[2] and \
  109. storedbbox[3] >= bbox[3]) or \
  110. self.request.upper() != "GETMAP":
  111. logging.info(
  112. "Using cached file for type [%s] with bbox [%f,%f,%f,%f], not downloading new data"%\
  113. (typename, bbox[0],bbox[1],bbox[2],bbox[3]))
  114. # setting output file name
  115. outfn = os.path.join(datadir,"%s.gml"%typename)
  116. else:
  117. # remove pre-cached file with only little area
  118. logging.info("Removing pre-cached files %s.*"% typename)
  119. self.__clear(datadir,typename)
  120. else:
  121. os.mkdir(os.path.join(self.cachedir,"cache"))
  122. # create new cache file, if it does not exist yet
  123. # download the data from the server
  124. if outfn == None:
  125. # clear, just to be sure
  126. self.__clear(datadir,typename)
  127. # create cached bbox
  128. outbbox = open(os.path.join(datadir,"%s.bbox"%typename),"w")
  129. outbbox.write("%f,%f,%f,%f"%(bbox[0],bbox[1],bbox[2],bbox[3]))
  130. outbbox.close()
  131. # create cached filter
  132. if fes:
  133. outfes = open(os.path.join(datadir,"%s.filter"%typename),"w")
  134. outfes.write(fes)
  135. outfes.close()
  136. # create chace file
  137. outfn= os.path.join(datadir,"%s.gml"%typename)
  138. #cachefile = open(os.path.join(datadir,"%s.gmlorig"%typename),"w")
  139. cachefile = open(outfn,"w")
  140. # download feature from WFS
  141. logging.debug("Downloading data [%s] from bbox [%f,%f,%f,%f]"%\
  142. (typename, bbox[0],bbox[1],bbox[2],bbox[3]))
  143. bbox.append(crs.getcode())
  144. feature = self.capabilities.getfeature(
  145. typename=typename,
  146. bbox=bbox,
  147. filter=fes,
  148. srsname=crs.getcode(),
  149. propertyname=None)
  150. # features are here
  151. # now take OGR and convert downloaded data into OGR-readable
  152. # format
  153. # this will ommit some GML-specific issues (like namespaces
  154. # etc.)
  155. # data will be stored into target file name
  156. cachefile.write(feature.read())
  157. cachefile.close()
  158. #ds = ogr.Open(cachefile.name)
  159. #drv = ogr.GetDriverByName(ds.GetDriver().name)
  160. #out = drv.CopyDataSource(ds,outfn)
  161. #out.Destroy()
  162. # check
  163. if self.request.upper() in ["GETMAP"]:
  164. check = ogr.Open(outfn)
  165. if not check:
  166. self.__clear(datadir,typename)
  167. else:
  168. check.Destroy()
  169. # set layer connection
  170. layerobj.connection = os.path.abspath(outfn)
  171. # data are downloaded
  172. # make sure, they have propper axis order - x,y
  173. if self.capabilities.version == "1.1.0" and crs.axisorder == "yx":
  174. pass
  175. logging.debug("Setting GML_INVERT_AXIS_ORDER_IF_LAT_LONG variable to YES")
  176. gdal.SetConfigOption("GML_INVERT_AXIS_ORDER_IF_LAT_LONG","YES")
  177. gdal.SetConfigOption("GML_CONSIDER_EPSG_AS_URN","YES")
  178. # >>> from osgeo import ogr
  179. # >>> from osgeo import gdal
  180. # >>> gdal.SetConfigOption("GML_INVERT_AXIS_ORDER_IF_LAT_LONG","YES")
  181. # >>> gdal.SetConfigOption("GML_CONSIDER_EPSG_AS_URN","YES")
  182. # >>> ind = ogr.Open("data.xml")
  183. # >>> outd = ogr.GetDriverByName("GML")
  184. # >>> outf = outd.CopyDataSource(ind,"out4.xml")
  185. # >>> outf.Destroy()
  186. logging.info("Connection of [%s] set to %s" % (typename,layerobj.connection))
  187. return outfn
  188. def __adjustBBox(self,bbox,src,target,version):
  189. """ adjust bounding coordinates and axis order
  190. """
  191. # swap axis, if needed
  192. if version == "1.3.0" and src.axisorder == "yx":
  193. bbox[0],bbox[1] = bbox[1],bbox[0]
  194. bbox[2],bbox[3] = bbox[3],bbox[2]
  195. # coordinate transformation
  196. projsrc = osr.SpatialReference()
  197. projsrc.ImportFromEPSG(src.code)
  198. projtarget = osr.SpatialReference()
  199. projtarget.ImportFromEPSG(target.code)
  200. trans = osr.CoordinateTransformation(projsrc,projtarget)
  201. bbox[0],bbox[1],z = trans.TransformPoint(float(bbox[0]),float(bbox[1]))
  202. bbox[2],bbox[3],z = trans.TransformPoint(float(bbox[2]),float(bbox[3]))
  203. return bbox
  204. def setFilter(self,mapobj,request):
  205. """ Set WFS filter encoding
  206. """
  207. # get the layer
  208. layerobj = mapobj.getLayerByName(request.getValueByName("layers"))
  209. # get the filter
  210. logging.debug("FES received from HSLayers: %s" % fes)
  211. # cut off the opening and closing <Filter> tag
  212. # - this is needed for mapserver
  213. #root = etree.XML(fes)
  214. #msFilter = ""
  215. #for child in root:
  216. # msFilter += etree.tostring(child)
  217. #logging.debug("Setting the filter %s" % msFilter)
  218. # set the filter
  219. layerobj.setMetaData("wfs_filter",fes)
  220. #layerobj.setMetaData("wfs_filter",msFilter)
  221. # save the mapfile - debugging
  222. mapobj.save("mapfile.fes")
  223. def makeMap(self,mapfilename=None):
  224. mapobj = self.getMapObj(mapfilename)
  225. # mapobjects exists, do not do any new layers
  226. if mapobj.numlayers:
  227. return mapobj
  228. self.layerDefFile = self.createLayerDefinitionFile("wfs",
  229. os.path.join( os.path.dirname(__file__), "templates",'wfs.xml'))
  230. ds = ogr.Open(self.layerDefFile)
  231. self.setMapName(mapobj)
  232. # load mapfile SYMBOLs
  233. symbolPath = os.path.join( os.path.dirname(__file__), "symbols.txt")
  234. symbolsLoaded = mapobj.setSymbolSet(symbolPath)
  235. if symbolsLoaded == mapscript.MS_SUCCESS:
  236. logging.debug("Symbols loaded from %s",symbolPath)
  237. else:
  238. logging.debug("Error loading symbols from %s",symbolPath)
  239. logging.debug(self.capabilities.contents)
  240. srss = []
  241. for name in self.capabilities.contents:
  242. mapobj.setMetaData("wms_srs",self.config.get("MapServer","srs"))
  243. mapobj.setMetaData("wfs_srs",self.config.get("MapServer","srs"))
  244. mapobj.setMetaData("wfs_connectiontimeout","90")
  245. layer = self.capabilities.contents[name]
  246. logging.debug("Creating layer %s" % name)
  247. srss = srss+filter(lambda y: not y in srss,layer.crsOptions)
  248. lyrobj = mapscript.layerObj(mapobj)
  249. #lyrobj.name = name.replace(":","_")
  250. lyrobj.name = name
  251. lyrobj.title = layer.title
  252. if layer.title:
  253. lyrobj.setMetaData("wms_title",layer.title)
  254. lyrobj.setMetaData("wfs_title",layer.title)
  255. #if layer.abstract:
  256. # lyrobj.setMetaData("ows_abstract", layer.abstract)
  257. logging.debug("WFS version %s",self.capabilities.version)
  258. lyrobj.setMetaData("gml_include_items","all")
  259. lyrobj.setConnectionType(mapscript.MS_OGR,'')
  260. lyrobj.data = re.sub(r".*:","",name)
  261. crs = self.__getLayerCrs(layer.crsOptions)
  262. if ds:
  263. ogrLayer = ds.GetLayerByName(name)
  264. extent = self.getLayerExtent(layer,crs)
  265. if extent:
  266. lyrobj.setMetaData("wms_extent","%s %s %s %s" % \
  267. (extent[0],extent[1],extent[2],extent[3]))
  268. lyrobj.setMetaData("wfs_extent","%s %s %s %s" % \
  269. (extent[0],extent[1],extent[2],extent[3]))
  270. lyrobj.type = self._getLayerType(ogrLayer)
  271. else:
  272. mapobj.removeLayer(mapobj.numlayers-1)
  273. logging.debug("No ogrDataSource found")
  274. continue
  275. lyrobj.setProjection(crs.getcode())
  276. #lyrobj.setProjection(layer.crsOptions[0].getcode())
  277. lyrobj.dump = mapscript.MS_TRUE
  278. lyrobj.template = "foo"
  279. cls = mapscript.classObj(lyrobj)
  280. style = mapscript.styleObj(cls)
  281. style.outlinecolor=mapscript.colorObj(134,81,0)
  282. style.color=mapscript.colorObj(238,153,0)
  283. style.size=5
  284. style.width=5
  285. if lyrobj.type == mapscript.MS_LAYER_POINT:
  286. style.symbol = 1
  287. ## overwrite already set SRSs
  288. #if len(srss) > 0:
  289. # logging.debug("Overwriting SRS option")
  290. # mapobj.setMetaData("wms_srs"," ".join(srss))
  291. self.saveMapfile(mapobj,mapfilename)
  292. return mapobj
  293. def getGeomName(self,geomname):
  294. if geomname.find("LINE") > -1:
  295. return mapscript.MS_LAYER_LINE
  296. elif geomname.find("POLYGON") > -1:
  297. return mapscript.MS_LAYER_POLYGON
  298. else:
  299. return mapscript.MS_LAYER_POINT
  300. def setMapName(self,mapobj):
  301. mapobj.name = self.config.get("MapServer","name")
  302. if self.capabilities.identification.title:
  303. mapobj.setMetaData("wms_title",self.capabilities.identification.title)
  304. mapobj.setMetaData("wfs_title",self.capabilities.identification.title)
  305. if self.capabilities.identification.abstract:
  306. mapobj.setMetaData("wms_abstract",self.capabilities.identification.abstract)
  307. mapobj.setMetaData("wfs_abstract",self.capabilities.identification.abstract)
  308. def _getLayerType(self,layer):
  309. """Returns MS layer type based on ogr.Layer.GetGeomType
  310. with ogr:
  311. wkbGeometryCollection = 7
  312. wkbGeometryCollection25D = -2147483641
  313. wkbLineString = 2
  314. wkbLineString25D = -2147483646
  315. wkbLinearRing = 101
  316. wkbMultiLineString = 5
  317. wkbMultiLineString25D = -2147483643
  318. wkbMultiPoint = 4
  319. wkbMultiPoint25D = -2147483644
  320. wkbMultiPolygon = 6
  321. wkbMultiPolygon25D = -2147483642
  322. wkbNDR = 1
  323. wkbNone = 100
  324. wkbPoint = 1
  325. wkbPoint25D = -2147483647
  326. wkbPolygon = 3
  327. wkbPolygon25D = -2147483645
  328. wkbUnknown = 0
  329. """
  330. geomType = layer.GetGeomType()
  331. if geomType == 0: # unknown
  332. # brutal force way
  333. f = layer.GetNextFeature()
  334. if f:
  335. gr = f.GetGeometryRef()
  336. geomType = gr.GetGeometryType()
  337. if geomType in [ogr.wkbPolygon,
  338. ogr.wkbMultiPolygon,
  339. ogr.wkbLinearRing]:
  340. return mapscript.MS_LAYER_POLYGON
  341. elif geomType in [ogr.wkbLineString,
  342. ogr.wkbMultiLineString]:
  343. return mapscript.MS_LAYER_LINE
  344. else:
  345. return mapscript.MS_LAYER_POINT
  346. def __getLayerCrs(self,crss):
  347. """
  348. Returns bests (non-degree) coordinate system of the layer, which is
  349. available.
  350. Ofcourse, sometimes, there is no other option, there EPSG:4326, but
  351. take somethign else, if you can
  352. """
  353. for crs in crss:
  354. if crs.getcode() == "EPSG:4326":
  355. return crs
  356. return crss[0]
  357. def __clear(self,datadir,typename):
  358. """Remove all cached files with following typename
  359. """
  360. for i in os.listdir(datadir):
  361. if i.find(typename) == 0:
  362. logging.debug("Removing pre-cached file %s"% i)
  363. os.remove(os.path.join(datadir,i))
  364. def __fixNamespace(self,typename):
  365. """Fix missing namespace in output XML
  366. """
  367. content = mapscript.msIO_getStdoutBufferBytes()
  368. # look after something like
  369. # <namespace:layerName_layer> and add namespace definition
  370. if typename.find(":") == -1:
  371. return content
  372. (prefix,name) = typename.split(":")
  373. namespace = self.capabilities._capabilities.nsmap[prefix]
  374. return content.replace("<%s:%s%s>"%(prefix,name,"_layer"),
  375. "<%s:%s%s xmlns:%s=\"%s\">"%(prefix,name,"_layer",prefix,namespace))