ws.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  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 sys
  14. import copy
  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.debug import Debug
  20. from core.gconsole import CmdThread, GStderr, EVT_CMD_DONE, EVT_CMD_OUTPUT
  21. from core.utils import _
  22. try:
  23. haveGdal = True
  24. from osgeo import gdal
  25. from osgeo import gdalconst
  26. except ImportError:
  27. haveGdal = False
  28. from grass.pydispatch.signal import Signal
  29. class RenderWMSMgr(wx.EvtHandler):
  30. """!Fetch and prepare WMS data for rendering.
  31. """
  32. def __init__(self, layer, 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.layer = layer
  37. wx.EvtHandler.__init__(self)
  38. # thread for d.wms commands
  39. self.thread = CmdThread(self)
  40. self.Bind(EVT_CMD_DONE, self.OnDataFetched)
  41. self.downloading = False
  42. self.renderedRegion = None
  43. self.updateMap = True
  44. self.fetched_data_cmd = None
  45. self.cmdStdErr = GStderr(self)
  46. self.mapfile = mapfile
  47. self.maskfile = maskfile
  48. self.tempMap = grass.tempfile()
  49. self.dstSize = {}
  50. self.Bind(EVT_CMD_OUTPUT, self.OnCmdOutput)
  51. self.dataFetched = Signal('RenderWMSMgr.dataFetched')
  52. self.updateProgress = Signal('RenderWMSMgr.updateProgress')
  53. def __del__(self):
  54. grass.try_remove(self.tempMap)
  55. def Render(self, cmd, env):
  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. env = copy.copy(env)
  63. self.dstSize['cols'] = int(env["GRASS_WIDTH"])
  64. self.dstSize['rows'] = int(env["GRASS_HEIGHT"])
  65. region = self._getRegionDict(env)
  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. env["GRASS_PNGFILE"] = self.tempMap
  97. env["GRASS_REGION"] = self._createRegionStr(region)
  98. self.thread.RunCmd(cmdList, env=env, stderr=self.cmdStdErr)
  99. def OnCmdOutput(self, event):
  100. """!Print cmd output according to debug level.
  101. """
  102. if Debug.GetLevel() == 0:
  103. if event.type == 'error':
  104. sys.stderr.write(event.text)
  105. sys.stderr.flush()
  106. else:
  107. Debug.msg(1, event.text)
  108. def OnDataFetched(self, event):
  109. """!Fetch data
  110. """
  111. if event.pid != self.currentPid:
  112. return
  113. self.downloading = False
  114. if not self.updateMap:
  115. self.updateProgress.emit(layer=self.layer)
  116. self.renderedRegion = None
  117. self.fetched_data_cmd = None
  118. return
  119. self.mapMerger = GDALRasterMerger(targetFile = self.mapfile, region = self.renderedRegion,
  120. bandsNum = 3, gdalDriver = 'PNM', fillValue = 0)
  121. self.mapMerger.AddRasterBands(self.tempMap, {1 : 1, 2 : 2, 3 : 3})
  122. del self.mapMerger
  123. self.maskMerger = GDALRasterMerger(targetFile = self.maskfile, region = self.renderedRegion,
  124. bandsNum = 1, gdalDriver = 'PNM', fillValue = 0)
  125. #{4 : 1} alpha channel (4) to first and only channel (1) in mask
  126. self.maskMerger.AddRasterBands(self.tempMap, {4 : 1})
  127. del self.maskMerger
  128. self.fetched_data_cmd = self.fetching_cmd
  129. self.dataFetched.emit()
  130. def _getRegionDict(self, env):
  131. """!Parse string from GRASS_REGION env variable into dict.
  132. """
  133. region = {}
  134. parsedRegion = env["GRASS_REGION"].split(';')
  135. for r in parsedRegion:
  136. r = r.split(':')
  137. r[0] = r[0].strip()
  138. if len(r) < 2:
  139. continue
  140. try:
  141. if r[0] in ['cols', 'rows']:
  142. region[r[0]] = int(r[1])
  143. else:
  144. region[r[0]] = float(r[1])
  145. except ValueError:
  146. region[r[0]] = r[1]
  147. return region
  148. def _createRegionStr(self, region):
  149. """!Create string for GRASS_REGION env variable from dict created by _getRegionDict.
  150. """
  151. regionStr = ''
  152. for k, v in region.iteritems():
  153. item = k + ': ' + str(v)
  154. if regionStr:
  155. regionStr += '; '
  156. regionStr += item
  157. return regionStr
  158. def IsDownloading(self):
  159. """!Is it downloading any data from WMS server?
  160. """
  161. return self.downloading
  162. def _fitAspect(self, region, size):
  163. """!Compute region parameters to have direction independent resolution.
  164. """
  165. if region['n-s resol'] > region['e-w resol']:
  166. delta = region['n-s resol'] * size['cols'] / 2
  167. center = (region['east'] - region['west'])/2
  168. region['east'] = center + delta + region['west']
  169. region['west'] = center - delta + region['west']
  170. region['e-w resol'] = region['n-s resol']
  171. else:
  172. delta = region['e-w resol'] * size['rows'] / 2
  173. center = (region['north'] - region['south'])/2
  174. region['north'] = center + delta + region['south']
  175. region['south'] = center - delta + region['south']
  176. region['n-s resol'] = region['e-w resol']
  177. def Abort(self):
  178. """!Abort process"""
  179. self.updateMap = False
  180. self.thread.abort(abortall = True)
  181. class GDALRasterMerger:
  182. """!Merge rasters.
  183. Based on gdal_merge.py utility.
  184. """
  185. def __init__(self, targetFile, region, bandsNum, gdalDriver, fillValue = None):
  186. """!Create raster for merging.
  187. """
  188. self.gdalDrvType = gdalDriver
  189. nsRes = (region['south'] - region['north']) / region['rows']
  190. ewRes = (region['east'] - region['west']) / region['cols']
  191. self.tGeotransform = [region['west'], ewRes, 0, region['north'], 0, nsRes]
  192. self.tUlx, self.tUly, self.tLrx, self.tLry = self._getCorners(self.tGeotransform, region)
  193. driver = gdal.GetDriverByName(self.gdalDrvType)
  194. self.tDataset = driver.Create(targetFile, region['cols'], region['rows'], bandsNum, gdal.GDT_Byte)
  195. if fillValue is not None:
  196. # fill raster bands with a constant value
  197. for iBand in range(1, self.tDataset.RasterCount + 1):
  198. self.tDataset.GetRasterBand(iBand).Fill(fillValue)
  199. def AddRasterBands(self, sourceFile, sTBands):
  200. """!Add raster bands from sourceFile into the merging raster.
  201. """
  202. sDataset = gdal.Open(sourceFile, gdal.GA_ReadOnly)
  203. if sDataset is None:
  204. return
  205. sGeotransform = sDataset.GetGeoTransform()
  206. sSize = {
  207. 'rows' : sDataset.RasterYSize,
  208. 'cols' : sDataset.RasterXSize
  209. }
  210. sUlx, sUly, sLrx, sLry = self._getCorners(sGeotransform, sSize)
  211. # figure out intersection region
  212. tIntsctUlx = max(self.tUlx,sUlx)
  213. tIntsctLrx = min(self.tLrx,sLrx)
  214. if self.tGeotransform[5] < 0:
  215. tIntsctUly = min(self.tUly,sUly)
  216. tIntsctLry = max(self.tLry,sLry)
  217. else:
  218. tIntsctUly = max(self.tUly,sUly)
  219. tIntsctLry = min(self.tLry,sLry)
  220. # do they even intersect?
  221. if tIntsctUlx >= tIntsctLrx:
  222. return
  223. if self.tGeotransform[5] < 0 and tIntsctUly <= tIntsctLry:
  224. return
  225. if self.tGeotransform[5] > 0 and tIntsctUly >= tIntsctLry:
  226. return
  227. # compute target window in pixel coordinates.
  228. tXoff = int((tIntsctUlx - self.tGeotransform[0]) / self.tGeotransform[1] + 0.1)
  229. tYoff = int((tIntsctUly - self.tGeotransform[3]) / self.tGeotransform[5] + 0.1)
  230. tXsize = int((tIntsctLrx - self.tGeotransform[0])/self.tGeotransform[1] + 0.5) - tXoff
  231. tYsize = int((tIntsctLry - self.tGeotransform[3])/self.tGeotransform[5] + 0.5) - tYoff
  232. if tXsize < 1 or tYsize < 1:
  233. return
  234. # Compute source window in pixel coordinates.
  235. sXoff = int((tIntsctUlx - sGeotransform[0]) / sGeotransform[1])
  236. sYoff = int((tIntsctUly - sGeotransform[3]) / sGeotransform[5])
  237. sXsize = int((tIntsctLrx - sGeotransform[0]) / sGeotransform[1] + 0.5) - sXoff
  238. sYsize = int((tIntsctLry - sGeotransform[3]) / sGeotransform[5] + 0.5) - sYoff
  239. if sXsize < 1 or sYsize < 1:
  240. return
  241. for sBandNnum, tBandNum in sTBands.iteritems():
  242. bandData = sDataset.GetRasterBand(sBandNnum).ReadRaster(sXoff, sYoff, sXsize,
  243. sYsize, tXsize, tYsize, gdal.GDT_Byte)
  244. self.tDataset.GetRasterBand(tBandNum).WriteRaster(tXoff, tYoff, tXsize, tYsize, bandData,
  245. tXsize, tYsize, gdal.GDT_Byte)
  246. def _getCorners(self, geoTrans, size):
  247. ulx = geoTrans[0]
  248. uly = geoTrans[3]
  249. lrx = geoTrans[0] + size['cols'] * geoTrans[1]
  250. lry = geoTrans[3] + size['rows'] * geoTrans[5]
  251. return ulx, uly, lrx, lry
  252. def SetGeorefAndProj(self):
  253. """!Set georeference and projection to target file
  254. """
  255. projection = grass.read_command('g.proj',
  256. flags = 'wf')
  257. self.tDataset.SetProjection(projection)
  258. self.tDataset.SetGeoTransform(self.tGeotransform)
  259. def __del__(self):
  260. self.tDataset = None