ws.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. """!
  2. @package core.ws
  3. @brief Fetching and preparation of web service data for rendering.
  4. Note: Currently only WMS is implemented.
  5. Classes:
  6. - ws::RenderWMSMgr
  7. - ws::GDALRasterMerger
  8. (C) 2012 by the GRASS Development Team
  9. This program is free software under the GNU General Public License
  10. (>=v2). Read the file COPYING that comes with GRASS for details.
  11. @author Stepan Turek <stepan.turek seznam.cz> (mentor: Martin Landa)
  12. """
  13. import os
  14. import sys
  15. import wx
  16. from grass.script import core as grass
  17. from core import utils
  18. from core.events import gUpdateMap
  19. from core.render import wxUpdateProgressBar
  20. from core.debug import Debug
  21. from core.gconsole import CmdThread, GStderr, EVT_CMD_DONE, EVT_CMD_OUTPUT
  22. from core.gcmd import GException
  23. try:
  24. haveGdal = True
  25. from osgeo import gdal
  26. from osgeo import gdalconst
  27. except ImportError:
  28. haveGdal = False
  29. class RenderWMSMgr(wx.EvtHandler):
  30. """!Fetch and prepare WMS data for rendering.
  31. """
  32. def __init__(self, receiver, layer, Map, mapfile, maskfile):
  33. if not haveGdal:
  34. sys.stderr.write(_("Unable to load GDAL Python bindings.\n"\
  35. "WMS layers can not be displayed without the bindings.\n"))
  36. self.Map = Map
  37. self.receiver = receiver
  38. self.layer = layer
  39. wx.EvtHandler.__init__(self)
  40. # thread for d.wms commands
  41. self.thread = CmdThread(self)
  42. self.Bind(EVT_CMD_DONE, self.OnDataFetched)
  43. self.downloading = False
  44. self.renderedRegion = None
  45. self.updateMap = True
  46. self.fetched_data_cmd = None
  47. self.cmdStdErr = GStderr(self)
  48. self.mapfile = mapfile
  49. self.maskfile = maskfile
  50. self.tempMap = grass.tempfile()
  51. self.dstSize = {}
  52. self.Bind(EVT_CMD_OUTPUT, self.OnCmdOutput)
  53. def __del__(self):
  54. grass.try_remove(self.tempMap)
  55. def Render(self, cmd):
  56. """!If it is needed, download missing WMS data.
  57. @todo lmgr deletes mapfile and maskfile when order of layers
  58. was changed (drag and drop) - if deleted, fetch data again
  59. """
  60. if not haveGdal:
  61. return
  62. self.dstSize['cols'] = int(os.environ["GRASS_WIDTH"])
  63. self.dstSize['rows'] = int(os.environ["GRASS_HEIGHT"])
  64. region = self._getRegionDict()
  65. self._fitAspect(region, self.dstSize)
  66. self.updateMap = True
  67. fetchData = False
  68. zoomChanged = False
  69. if self.renderedRegion is None or \
  70. cmd != self.fetched_data_cmd:
  71. fetchData = True
  72. else:
  73. for c in ['north', 'south', 'east', 'west']:
  74. if self.renderedRegion and \
  75. region[c] != self.renderedRegion[c]:
  76. fetchData = True
  77. break
  78. for c in ['e-w resol', 'n-s resol']:
  79. if self.renderedRegion and \
  80. region[c] != self.renderedRegion[c]:
  81. zoomChanged = True
  82. break
  83. if fetchData:
  84. self.fetched_data_cmd = None
  85. self.renderedRegion = region
  86. grass.try_remove(self.mapfile)
  87. grass.try_remove(self.tempMap)
  88. self.currentPid = self.thread.GetId()
  89. self.thread.abort()
  90. self.downloading = True
  91. self.fetching_cmd = cmd
  92. cmdList = utils.CmdTupleToList(cmd)
  93. if Debug.GetLevel() < 3:
  94. cmdList.append('--quiet')
  95. tempPngfile = None
  96. if "GRASS_PNGFILE" in os.environ:
  97. tempPngfile = os.environ["GRASS_PNGFILE"]
  98. os.environ["GRASS_PNGFILE"] = self.tempMap
  99. tempRegion = os.environ["GRASS_REGION"]
  100. os.environ["GRASS_REGION"] = self._createRegionStr(region)
  101. self.thread.RunCmd(cmdList, env = os.environ.copy(), stderr = self.cmdStdErr)
  102. os.environ.pop("GRASS_PNGFILE")
  103. if tempPngfile:
  104. os.environ["GRASS_PNGFILE"] = tempPngfile
  105. os.environ["GRASS_REGION"] = tempRegion
  106. def OnCmdOutput(self, event):
  107. """!Print cmd output according to debug level.
  108. """
  109. if Debug.GetLevel() == 0:
  110. if event.type == 'error':
  111. sys.stderr.write(event.text)
  112. sys.stderr.flush()
  113. else:
  114. Debug.msg(1, event.text)
  115. def OnDataFetched(self, event):
  116. """!Fetch data
  117. """
  118. if event.pid != self.currentPid:
  119. return
  120. self.downloading = False
  121. if not self.updateMap:
  122. if self.receiver:
  123. event = wxUpdateProgressBar(layer = self.layer, map = self.Map)
  124. self.receiver.GetEventHandler().ProcessEvent(event)
  125. self.renderedRegion = None
  126. self.fetched_data_cmd = None
  127. return
  128. self.mapMerger = GDALRasterMerger(targetFile = self.mapfile, region = self.renderedRegion,
  129. bandsNum = 3, gdalDriver = 'PNM', fillValue = 0)
  130. self.mapMerger.AddRasterBands(self.tempMap, {1 : 1, 2 : 2, 3 : 3})
  131. del self.mapMerger
  132. self.maskMerger = GDALRasterMerger(targetFile = self.maskfile, region = self.renderedRegion,
  133. bandsNum = 1, gdalDriver = 'PNM', fillValue = 0)
  134. #{4 : 1} alpha channel (4) to first and only channel (1) in mask
  135. self.maskMerger.AddRasterBands(self.tempMap, {4 : 1})
  136. del self.maskMerger
  137. self.fetched_data_cmd = self.fetching_cmd
  138. if self.receiver:
  139. event = gUpdateMap()
  140. wx.PostEvent(self.receiver, event)
  141. def _getRegionDict(self):
  142. """!Parse string from GRASS_REGION env variable into dict.
  143. """
  144. region = {}
  145. parsedRegion = os.environ["GRASS_REGION"].split(';')
  146. for r in parsedRegion:
  147. r = r.split(':')
  148. r[0] = r[0].strip()
  149. if len(r) < 2:
  150. continue
  151. try:
  152. if r[0] in ['cols', 'rows']:
  153. region[r[0]] = int(r[1])
  154. else:
  155. region[r[0]] = float(r[1])
  156. except ValueError:
  157. region[r[0]] = r[1]
  158. return region
  159. def _createRegionStr(self, region):
  160. """!Create string for GRASS_REGION env variable from dict created by _getRegionDict.
  161. """
  162. regionStr = ''
  163. for k, v in region.iteritems():
  164. item = k + ': ' + str(v)
  165. if regionStr:
  166. regionStr += '; '
  167. regionStr += item
  168. return regionStr
  169. def IsDownloading(self):
  170. """!Is it downloading any data from WMS server?
  171. """
  172. return self.downloading
  173. def _fitAspect(self, region, size):
  174. """!Compute region parameters to have direction independent resolution.
  175. """
  176. if region['n-s resol'] > region['e-w resol']:
  177. delta = region['n-s resol'] * size['cols'] / 2
  178. center = (region['east'] - region['west'])/2
  179. region['east'] = center + delta + region['west']
  180. region['west'] = center - delta + region['west']
  181. region['e-w resol'] = region['n-s resol']
  182. else:
  183. delta = region['e-w resol'] * size['rows'] / 2
  184. center = (region['north'] - region['south'])/2
  185. region['north'] = center + delta + region['south']
  186. region['south'] = center - delta + region['south']
  187. region['n-s resol'] = region['e-w resol']
  188. def Abort(self):
  189. """!Abort process"""
  190. self.updateMap = False
  191. self.thread.abort(abortall = True)
  192. def SetReceiver(self, receiver):
  193. """!Set events receiver
  194. @todo If it will be needed to change receiver, take care of running threads.
  195. """
  196. self.receiver = receiver
  197. class GDALRasterMerger:
  198. """!Merge rasters.
  199. Based on gdal_merge.py utility.
  200. """
  201. def __init__(self, targetFile, region, bandsNum, gdalDriver, fillValue = None):
  202. """!Create raster for merging.
  203. """
  204. self.gdalDrvType = gdalDriver
  205. nsRes = (region['south'] - region['north']) / region['rows']
  206. ewRes = (region['east'] - region['west']) / region['cols']
  207. self.tGeotransform = [region['west'], ewRes, 0, region['north'], 0, nsRes]
  208. self.tUlx, self.tUly, self.tLrx, self.tLry = self._getCorners(self.tGeotransform, region)
  209. driver = gdal.GetDriverByName(self.gdalDrvType)
  210. self.tDataset = driver.Create(targetFile, region['cols'], region['rows'], bandsNum, gdal.GDT_Byte)
  211. if fillValue is not None:
  212. # fill raster bands with a constant value
  213. for iBand in range(1, self.tDataset.RasterCount + 1):
  214. self.tDataset.GetRasterBand(iBand).Fill(fillValue)
  215. def AddRasterBands(self, sourceFile, sTBands):
  216. """!Add raster bands from sourceFile into the merging raster.
  217. """
  218. sDataset = gdal.Open(sourceFile, gdal.GA_ReadOnly)
  219. if sDataset is None:
  220. return
  221. sGeotransform = sDataset.GetGeoTransform()
  222. sSize = {
  223. 'rows' : sDataset.RasterYSize,
  224. 'cols' : sDataset.RasterXSize
  225. }
  226. sUlx, sUly, sLrx, sLry = self._getCorners(sGeotransform, sSize)
  227. # figure out intersection region
  228. tIntsctUlx = max(self.tUlx,sUlx)
  229. tIntsctLrx = min(self.tLrx,sLrx)
  230. if self.tGeotransform[5] < 0:
  231. tIntsctUly = min(self.tUly,sUly)
  232. tIntsctLry = max(self.tLry,sLry)
  233. else:
  234. tIntsctUly = max(self.tUly,sUly)
  235. tIntsctLry = min(self.tLry,sLry)
  236. # do they even intersect?
  237. if tIntsctUlx >= tIntsctLrx:
  238. return
  239. if self.tGeotransform[5] < 0 and tIntsctUly <= tIntsctLry:
  240. return
  241. if self.tGeotransform[5] > 0 and tIntsctUly >= tIntsctLry:
  242. return
  243. # compute target window in pixel coordinates.
  244. tXoff = int((tIntsctUlx - self.tGeotransform[0]) / self.tGeotransform[1] + 0.1)
  245. tYoff = int((tIntsctUly - self.tGeotransform[3]) / self.tGeotransform[5] + 0.1)
  246. tXsize = int((tIntsctLrx - self.tGeotransform[0])/self.tGeotransform[1] + 0.5) - tXoff
  247. tYsize = int((tIntsctLry - self.tGeotransform[3])/self.tGeotransform[5] + 0.5) - tYoff
  248. if tXsize < 1 or tYsize < 1:
  249. return
  250. # Compute source window in pixel coordinates.
  251. sXoff = int((tIntsctUlx - sGeotransform[0]) / sGeotransform[1])
  252. sYoff = int((tIntsctUly - sGeotransform[3]) / sGeotransform[5])
  253. sXsize = int((tIntsctLrx - sGeotransform[0]) / sGeotransform[1] + 0.5) - sXoff
  254. sYsize = int((tIntsctLry - sGeotransform[3]) / sGeotransform[5] + 0.5) - sYoff
  255. if sXsize < 1 or sYsize < 1:
  256. return
  257. for sBandNnum, tBandNum in sTBands.iteritems():
  258. bandData = sDataset.GetRasterBand(sBandNnum).ReadRaster(sXoff, sYoff, sXsize,
  259. sYsize, tXsize, tYsize, gdal.GDT_Byte)
  260. self.tDataset.GetRasterBand(tBandNum).WriteRaster(tXoff, tYoff, tXsize, tYsize, bandData,
  261. tXsize, tYsize, gdal.GDT_Byte)
  262. def _getCorners(self, geoTrans, size):
  263. ulx = geoTrans[0]
  264. uly = geoTrans[3]
  265. lrx = geoTrans[0] + size['cols'] * geoTrans[1]
  266. lry = geoTrans[3] + size['rows'] * geoTrans[5]
  267. return ulx, uly, lrx, lry
  268. def SetGeorefAndProj(self):
  269. """!Set georeference and projection to target file
  270. """
  271. projection = grass.read_command('g.proj',
  272. flags = 'wf')
  273. self.tDataset.SetProjection(projection)
  274. self.tDataset.SetGeoTransform(self.tGeotransform)
  275. def __del__(self):
  276. self.tDataset = None