ws.py 12 KB

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