__init__.py 17 KB

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