ws.py 11 KB

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