__init__.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  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]:
  110. logging.info(
  111. "Using cached file for type [%s] with bbox [%f,%f,%f,%f], not downloading new data"%\
  112. (typename, bbox[0],bbox[1],bbox[2],bbox[3]))
  113. # setting output file name
  114. outfn = os.path.join(datadir,"%s.gml"%typename)
  115. else:
  116. # remove pre-cached file with only little area
  117. logging.info("Removing pre-cached files %s.*"% typename)
  118. self.__clear(datadir,typename)
  119. else:
  120. os.mkdir(os.path.join(self.cachedir,"cache"))
  121. # create new cache file, if it does not exist yet
  122. # download the data from the server
  123. if outfn == None:
  124. # clear, just to be sure
  125. self.__clear(datadir,typename)
  126. # create cached bbox
  127. outbbox = open(os.path.join(datadir,"%s.bbox"%typename),"w")
  128. outbbox.write("%f,%f,%f,%f"%(bbox[0],bbox[1],bbox[2],bbox[3]))
  129. outbbox.close()
  130. # create cached filter
  131. if fes:
  132. outfes = open(os.path.join(datadir,"%s.filter"%typename),"w")
  133. outfes.write(fes)
  134. outfes.close()
  135. # create chace file
  136. outfn= os.path.join(datadir,"%s.gml"%typename)
  137. #cachefile = open(os.path.join(datadir,"%s.gmlorig"%typename),"w")
  138. cachefile = open(outfn,"w")
  139. # download feature from WFS
  140. logging.debug("Downloading data [%s] from bbox [%f,%f,%f,%f]"%\
  141. (typename, bbox[0],bbox[1],bbox[2],bbox[3]))
  142. bbox.append(crs.getcode())
  143. feature = self.capabilities.getfeature(
  144. typename=typename,
  145. bbox=bbox,
  146. filter=fes,
  147. srsname=crs.getcode(),
  148. propertyname=None)
  149. # features are here
  150. # now take OGR and convert downloaded data into OGR-readable
  151. # format
  152. # this will ommit some GML-specific issues (like namespaces
  153. # etc.)
  154. # data will be stored into target file name
  155. cachefile.write(feature.read())
  156. cachefile.close()
  157. # check
  158. check = ogr.Open(cachefile.name)
  159. if not check:
  160. self.__clear(datadir,typename)
  161. else:
  162. check.Destroy()
  163. #ds = ogr.Open(cachefile.name)
  164. #drv = ogr.GetDriverByName(ds.GetDriver().name)
  165. #out = drv.CopyDataSource(ds,outfn)
  166. #out.Destroy()
  167. # set layer connection
  168. layerobj.connection = os.path.abspath(outfn)
  169. # data are downloaded
  170. # make sure, they have propper axis order - x,y
  171. if self.capabilities.version == "1.1.0" and crs.axisorder == "yx":
  172. pass
  173. logging.debug("Setting GML_INVERT_AXIS_ORDER_IF_LAT_LONG variable to YES")
  174. gdal.SetConfigOption("GML_INVERT_AXIS_ORDER_IF_LAT_LONG","YES")
  175. gdal.SetConfigOption("GML_CONSIDER_EPSG_AS_URN","YES")
  176. # >>> from osgeo import ogr
  177. # >>> from osgeo import gdal
  178. # >>> gdal.SetConfigOption("GML_INVERT_AXIS_ORDER_IF_LAT_LONG","YES")
  179. # >>> gdal.SetConfigOption("GML_CONSIDER_EPSG_AS_URN","YES")
  180. # >>> ind = ogr.Open("data.xml")
  181. # >>> outd = ogr.GetDriverByName("GML")
  182. # >>> outf = outd.CopyDataSource(ind,"out4.xml")
  183. # >>> outf.Destroy()
  184. logging.info("Connection of [%s] set to %s" % (typename,layerobj.connection))
  185. return outfn
  186. def __adjustBBox(self,bbox,src,target,version):
  187. """ adjust bounding coordinates and axis order
  188. """
  189. # swap axis, if needed
  190. if version == "1.3.0" and src.axisorder == "yx":
  191. bbox[0],bbox[1] = bbox[1],bbox[0]
  192. bbox[2],bbox[3] = bbox[3],bbox[2]
  193. # coordinate transformation
  194. projsrc = osr.SpatialReference()
  195. projsrc.ImportFromEPSG(src.code)
  196. projtarget = osr.SpatialReference()
  197. projtarget.ImportFromEPSG(target.code)
  198. trans = osr.CoordinateTransformation(projsrc,projtarget)
  199. bbox[0],bbox[1],z = trans.TransformPoint(float(bbox[0]),float(bbox[1]))
  200. bbox[2],bbox[3],z = trans.TransformPoint(float(bbox[2]),float(bbox[3]))
  201. return bbox
  202. def setFilter(self,mapobj,request):
  203. """ Set WFS filter encoding
  204. """
  205. # get the layer
  206. layerobj = mapobj.getLayerByName(request.getValueByName("layers"))
  207. # get the filter
  208. logging.debug("FES received from HSLayers: %s" % fes)
  209. # cut off the opening and closing <Filter> tag
  210. # - this is needed for mapserver
  211. #root = etree.XML(fes)
  212. #msFilter = ""
  213. #for child in root:
  214. # msFilter += etree.tostring(child)
  215. #logging.debug("Setting the filter %s" % msFilter)
  216. # set the filter
  217. layerobj.setMetaData("wfs_filter",fes)
  218. #layerobj.setMetaData("wfs_filter",msFilter)
  219. # save the mapfile - debugging
  220. mapobj.save("mapfile.fes")
  221. def makeMap(self,mapfilename=None):
  222. mapobj = self.getMapObj(mapfilename)
  223. # mapobjects exists, do not do any new layers
  224. if mapobj.numlayers:
  225. return mapobj
  226. self.layerDefFile = self.createLayerDefinitionFile("wfs",
  227. os.path.join( os.path.dirname(__file__), "templates",'wfs.xml'))
  228. ds = ogr.Open(self.layerDefFile)
  229. self.setMapName(mapobj)
  230. # load mapfile SYMBOLs
  231. symbolPath = os.path.join( os.path.dirname(__file__), "symbols.txt")
  232. symbolsLoaded = mapobj.setSymbolSet(symbolPath)
  233. if symbolsLoaded == mapscript.MS_SUCCESS:
  234. logging.debug("Symbols loaded from %s",symbolPath)
  235. else:
  236. logging.debug("Error loading symbols from %s",symbolPath)
  237. logging.debug(self.capabilities.contents)
  238. srss = []
  239. for name in self.capabilities.contents:
  240. mapobj.setMetaData("wms_srs",self.config.get("MapServer","srs"))
  241. mapobj.setMetaData("wfs_srs",self.config.get("MapServer","srs"))
  242. mapobj.setMetaData("wfs_connectiontimeout","90")
  243. layer = self.capabilities.contents[name]
  244. logging.debug("Creating layer %s" % name)
  245. srss = srss+filter(lambda y: not y in srss,layer.crsOptions)
  246. lyrobj = mapscript.layerObj(mapobj)
  247. #lyrobj.name = name.replace(":","_")
  248. lyrobj.name = name
  249. lyrobj.title = layer.title
  250. if layer.title:
  251. lyrobj.setMetaData("wms_title",layer.title)
  252. lyrobj.setMetaData("wfs_title",layer.title)
  253. #if layer.abstract:
  254. # lyrobj.setMetaData("ows_abstract", layer.abstract)
  255. logging.debug("WFS version %s",self.capabilities.version)
  256. lyrobj.setMetaData("gml_include_items","all")
  257. lyrobj.setConnectionType(mapscript.MS_OGR,'')
  258. lyrobj.data = re.sub(r".*:","",name)
  259. crs = self.__getLayerCrs(layer.crsOptions)
  260. if ds:
  261. ogrLayer = ds.GetLayerByName(name)
  262. extent = self.getLayerExtent(layer,crs)
  263. if extent:
  264. lyrobj.setMetaData("wms_extent","%s %s %s %s" % \
  265. (extent[0],extent[1],extent[2],extent[3]))
  266. lyrobj.setMetaData("wfs_extent","%s %s %s %s" % \
  267. (extent[0],extent[1],extent[2],extent[3]))
  268. lyrobj.type = self._getLayerType(ogrLayer)
  269. else:
  270. mapobj.removeLayer(mapobj.numlayers-1)
  271. logging.debug("No ogrDataSource found")
  272. continue
  273. lyrobj.setProjection(crs.getcode())
  274. #lyrobj.setProjection(layer.crsOptions[0].getcode())
  275. lyrobj.dump = mapscript.MS_TRUE
  276. lyrobj.template = "foo"
  277. cls = mapscript.classObj(lyrobj)
  278. style = mapscript.styleObj(cls)
  279. style.outlinecolor=mapscript.colorObj(134,81,0)
  280. style.color=mapscript.colorObj(238,153,0)
  281. style.size=5
  282. style.width=5
  283. if lyrobj.type == mapscript.MS_LAYER_POINT:
  284. style.symbol = 1
  285. ## overwrite already set SRSs
  286. #if len(srss) > 0:
  287. # logging.debug("Overwriting SRS option")
  288. # mapobj.setMetaData("wms_srs"," ".join(srss))
  289. self.saveMapfile(mapobj,mapfilename)
  290. return mapobj
  291. def getGeomName(self,geomname):
  292. if geomname.find("LINE") > -1:
  293. return mapscript.MS_LAYER_LINE
  294. elif geomname.find("POLYGON") > -1:
  295. return mapscript.MS_LAYER_POLYGON
  296. else:
  297. return mapscript.MS_LAYER_POINT
  298. def setMapName(self,mapobj):
  299. mapobj.name = self.config.get("MapServer","name")
  300. if self.capabilities.identification.title:
  301. mapobj.setMetaData("wms_title",self.capabilities.identification.title)
  302. mapobj.setMetaData("wfs_title",self.capabilities.identification.title)
  303. if self.capabilities.identification.abstract:
  304. mapobj.setMetaData("wms_abstract",self.capabilities.identification.abstract)
  305. mapobj.setMetaData("wfs_abstract",self.capabilities.identification.abstract)
  306. def _getLayerType(self,layer):
  307. """Returns MS layer type based on ogr.Layer.GetGeomType
  308. with ogr:
  309. wkbGeometryCollection = 7
  310. wkbGeometryCollection25D = -2147483641
  311. wkbLineString = 2
  312. wkbLineString25D = -2147483646
  313. wkbLinearRing = 101
  314. wkbMultiLineString = 5
  315. wkbMultiLineString25D = -2147483643
  316. wkbMultiPoint = 4
  317. wkbMultiPoint25D = -2147483644
  318. wkbMultiPolygon = 6
  319. wkbMultiPolygon25D = -2147483642
  320. wkbNDR = 1
  321. wkbNone = 100
  322. wkbPoint = 1
  323. wkbPoint25D = -2147483647
  324. wkbPolygon = 3
  325. wkbPolygon25D = -2147483645
  326. wkbUnknown = 0
  327. """
  328. geomType = layer.GetGeomType()
  329. if geomType == 0: # unknown
  330. # brutal force way
  331. f = layer.GetNextFeature()
  332. if f:
  333. gr = f.GetGeometryRef()
  334. geomType = gr.GetGeometryType()
  335. if geomType in [ogr.wkbPolygon,
  336. ogr.wkbMultiPolygon,
  337. ogr.wkbLinearRing]:
  338. return mapscript.MS_LAYER_POLYGON
  339. elif geomType in [ogr.wkbLineString,
  340. ogr.wkbMultiLineString]:
  341. return mapscript.MS_LAYER_LINE
  342. else:
  343. return mapscript.MS_LAYER_POINT
  344. def __getLayerCrs(self,crss):
  345. """
  346. Returns bests (non-degree) coordinate system of the layer, which is
  347. available.
  348. Ofcourse, sometimes, there is no other option, there EPSG:4326, but
  349. take somethign else, if you can
  350. """
  351. for crs in crss:
  352. if crs.getcode() == "EPSG:4326":
  353. return crs
  354. return crss[0]
  355. def __clear(self,datadir,typename):
  356. """Remove all cached files with following typename
  357. """
  358. for i in os.listdir(datadir):
  359. if i.find(typename) == 0:
  360. logging.debug("Removing pre-cached file %s"% i)
  361. os.remove(os.path.join(datadir,i))
  362. def __fixNamespace(self,typename):
  363. """Fix missing namespace in output XML
  364. """
  365. content = mapscript.msIO_getStdoutBufferBytes()
  366. # look after something like
  367. # <namespace:layerName_layer> and add namespace definition
  368. if typename.find(":") == -1:
  369. return content
  370. (prefix,name) = typename.split(":")
  371. namespace = self.capabilities._capabilities.nsmap[prefix]
  372. return content.replace("<%s:%s%s>"%(prefix,name,"_layer"),
  373. "<%s:%s%s xmlns:%s=\"%s\">"%(prefix,name,"_layer",prefix,namespace))