__init__.py 16 KB

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