__init__.py 17 KB

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