ws.py 11 KB

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