ws.py 11 KB

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