ws.py 12 KB

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