ws.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  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::StdErr
  8. - ws::GDALRasterMerger
  9. (C) 2012 by the GRASS Development Team
  10. This program is free software under the GNU General Public License
  11. (>=v2). Read the file COPYING that comes with GRASS for details.
  12. @author Stepan Turek <stepan.turek seznam.cz> (mentor: Martin Landa)
  13. """
  14. import os
  15. import sys
  16. import wx
  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, EVT_CMD_DONE
  21. from core.gcmd import GException
  22. try:
  23. haveGdal = True
  24. from osgeo import gdal
  25. from osgeo import gdalconst
  26. except ImportError:
  27. haveGdal = False
  28. class RenderWMSMgr(wx.EvtHandler):
  29. """!Fetch and prepare WMS data for rendering.
  30. @todo statusbar: updtade by event or call method ???
  31. """
  32. def __init__(self, parent, 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.parent = parent
  37. # thread for d.wms commands
  38. self.thread = CmdThread(self)
  39. wx.EvtHandler.__init__(self)
  40. self.Bind(EVT_CMD_DONE, self.OnDataFetched)
  41. self.downloading = False
  42. self.renderedRegion = None
  43. self.updateMap = True
  44. self.cmdStdErr = StdErr()
  45. self.mapfile = mapfile
  46. self.maskfile = maskfile
  47. self.tempMap = grass.tempfile()
  48. self.dstSize = {}
  49. def __del__(self):
  50. grass.try_remove(self.tempMap)
  51. def Render(self, cmd):
  52. """!If it is needed, download missing WMS data.
  53. @todo lmgr deletes mapfile and maskfile when order of layers
  54. was changed (drag and drop) - if deleted, fetch data again
  55. """
  56. if not haveGdal:
  57. return
  58. self.dstSize['cols'] = int(os.environ["GRASS_WIDTH"])
  59. self.dstSize['rows'] = int(os.environ["GRASS_HEIGHT"])
  60. region = self._getRegionDict()
  61. self._fitAspect(region, self.dstSize)
  62. self.updateMap = True
  63. fetchData = False
  64. zoomChanged = False
  65. if self.renderedRegion is None:
  66. fetchData = True
  67. else:
  68. for c in ['north', 'south', 'east', 'west']:
  69. if self.renderedRegion and \
  70. region[c] != self.renderedRegion[c]:
  71. fetchData = True
  72. break
  73. for c in ['e-w resol', 'n-s resol']:
  74. if self.renderedRegion and \
  75. region[c] != self.renderedRegion[c]:
  76. zoomChanged = True
  77. break
  78. if fetchData:
  79. self.renderedRegion = region
  80. grass.try_remove(self.mapfile)
  81. grass.try_remove(self.tempMap)
  82. self.currentPid = self.thread.GetId()
  83. self.thread.abort()
  84. self.downloading = True
  85. cmdList = utils.CmdTupleToList(cmd)
  86. if Debug.GetLevel() < 3:
  87. cmdList.append('--quiet')
  88. tempPngfile = os.environ["GRASS_PNGFILE"]
  89. os.environ["GRASS_PNGFILE"] = self.tempMap
  90. tempRegion = os.environ["GRASS_REGION"]
  91. os.environ["GRASS_REGION"] = self._createRegionStr(region)
  92. self.thread.RunCmd(cmdList, env = os.environ.copy(), stderr = self.cmdStdErr)
  93. os.environ["GRASS_PNGFILE"] = tempPngfile
  94. os.environ["GRASS_REGION"] = tempRegion
  95. def OnDataFetched(self, event):
  96. """!Fetch data
  97. @todo ?
  98. @todo needs refactoring - self.parent.parent.mapWin.UpdateMap
  99. """
  100. if event.pid != self.currentPid:
  101. return
  102. self.downloading = False
  103. if not self.updateMap:
  104. # TODO
  105. self.parent.parent.GetParentMapWindow().frame.GetProgressBar().UpdateProgress(self.parent, self.parent.parent)
  106. self.renderedRegion = None
  107. return
  108. self.mapMerger = GDALRasterMerger(targetFile = self.mapfile, region = self.renderedRegion,
  109. bandsNum = 3, gdalDriver = 'PNM', fillValue = 0)
  110. self.mapMerger.AddRasterBands(self.tempMap, {1 : 1, 2 : 2, 3 : 3})
  111. del self.mapMerger
  112. self.maskMerger = GDALRasterMerger(targetFile = self.maskfile, region = self.renderedRegion,
  113. bandsNum = 1, gdalDriver = 'PNM', fillValue = 0)
  114. #{4 : 1} alpha channel (4) to first and only channel (1) in mask
  115. self.maskMerger.AddRasterBands(self.tempMap, {4 : 1})
  116. del self.maskMerger
  117. self.parent.parent.GetParentMapWindow().UpdateMap(render = True)
  118. def _getRegionDict(self):
  119. """!Parse string from GRASS_REGION env variable into dict.
  120. """
  121. region = {}
  122. parsedRegion = os.environ["GRASS_REGION"].split(';')
  123. for r in parsedRegion:
  124. r = r.split(':')
  125. r[0] = r[0].strip()
  126. if len(r) < 2:
  127. continue
  128. try:
  129. if r[0] in ['cols', 'rows']:
  130. region[r[0]] = int(r[1])
  131. else:
  132. region[r[0]] = float(r[1])
  133. except ValueError:
  134. region[r[0]] = r[1]
  135. return region
  136. def _createRegionStr(self, region):
  137. """!Create string for GRASS_REGION env variable from dict created by _getRegionDict.
  138. """
  139. regionStr = ''
  140. for k, v in region.iteritems():
  141. item = k + ': ' + str(v)
  142. if regionStr:
  143. regionStr += '; '
  144. regionStr += item
  145. return regionStr
  146. def IsDownloading(self):
  147. """!Is it downloading any data from WMS server?
  148. """
  149. return self.downloading
  150. def _fitAspect(self, region, size):
  151. """!Compute region parameters to have direction independent resolution.
  152. """
  153. if region['n-s resol'] > region['e-w resol']:
  154. delta = region['n-s resol'] * size['cols'] / 2
  155. center = (region['east'] - region['west'])/2
  156. region['east'] = center + delta + region['west']
  157. region['west'] = center - delta + region['west']
  158. region['e-w resol'] = region['n-s resol']
  159. else:
  160. delta = region['e-w resol'] * size['rows'] / 2
  161. center = (region['north'] - region['south'])/2
  162. region['north'] = center + delta + region['south']
  163. region['south'] = center - delta + region['south']
  164. region['n-s resol'] = region['e-w resol']
  165. def Abort(self):
  166. """!Abort process"""
  167. self.updateMap = False
  168. self.thread.abort(abortall = True)
  169. class StdErr:
  170. """!Redirect error output according to debug mode.
  171. @todo: replace or move to the other module (gconsole???)
  172. """
  173. def flush(self):
  174. pass
  175. def write(self, s):
  176. if "GtkPizza" in s:
  177. return
  178. if Debug.GetLevel() == 0:
  179. message = ''
  180. for line in s.splitlines():
  181. if len(line) == 0:
  182. continue
  183. if 'GRASS_INFO_ERROR' in line:
  184. message += line.split(':', 1)[1].strip() + '\n'
  185. elif 'ERROR:' in line:
  186. message = line
  187. if message:
  188. sys.stderr.write(message)
  189. message = ''
  190. sys.stderr.flush()
  191. elif Debug.GetLevel() >= 1:
  192. for line in s.splitlines():
  193. if len(line) == 0:
  194. continue
  195. Debug.msg(3, line)
  196. class GDALRasterMerger:
  197. """!Merge rasters.
  198. Based on gdal_merge.py utility.
  199. """
  200. def __init__(self, targetFile, region, bandsNum, gdalDriver, fillValue = None):
  201. """!Create raster for merging.
  202. """
  203. self.gdalDrvType = gdalDriver
  204. nsRes = (region['south'] - region['north']) / region['rows']
  205. ewRes = (region['east'] - region['west']) / region['cols']
  206. self.tGeotransform = [region['west'], ewRes, 0, region['north'], 0, nsRes]
  207. self.tUlx, self.tUly, self.tLrx, self.tLry = self._getCorners(self.tGeotransform, region)
  208. driver = gdal.GetDriverByName(self.gdalDrvType)
  209. self.tDataset = driver.Create(targetFile, region['cols'], region['rows'], bandsNum, gdal.GDT_Byte)
  210. if fillValue is not None:
  211. # fill raster bands with a constant value
  212. for iBand in range(1, self.tDataset.RasterCount + 1):
  213. self.tDataset.GetRasterBand(iBand).Fill(fillValue)
  214. def AddRasterBands(self, sourceFile, sTBands):
  215. """!Add raster bands from sourceFile into the merging raster.
  216. """
  217. sDataset = gdal.Open(sourceFile, gdal.GA_ReadOnly)
  218. if sDataset is None:
  219. return
  220. sGeotransform = sDataset.GetGeoTransform()
  221. sSize = {
  222. 'rows' : sDataset.RasterYSize,
  223. 'cols' : sDataset.RasterXSize
  224. }
  225. sUlx, sUly, sLrx, sLry = self._getCorners(sGeotransform, sSize)
  226. # figure out intersection region
  227. tIntsctUlx = max(self.tUlx,sUlx)
  228. tIntsctLrx = min(self.tLrx,sLrx)
  229. if self.tGeotransform[5] < 0:
  230. tIntsctUly = min(self.tUly,sUly)
  231. tIntsctLry = max(self.tLry,sLry)
  232. else:
  233. tIntsctUly = max(self.tUly,sUly)
  234. tIntsctLry = min(self.tLry,sLry)
  235. # do they even intersect?
  236. if tIntsctUlx >= tIntsctLrx:
  237. return
  238. if self.tGeotransform[5] < 0 and tIntsctUly <= tIntsctLry:
  239. return
  240. if self.tGeotransform[5] > 0 and tIntsctUly >= tIntsctLry:
  241. return
  242. # compute target window in pixel coordinates.
  243. tXoff = int((tIntsctUlx - self.tGeotransform[0]) / self.tGeotransform[1] + 0.1)
  244. tYoff = int((tIntsctUly - self.tGeotransform[3]) / self.tGeotransform[5] + 0.1)
  245. tXsize = int((tIntsctLrx - self.tGeotransform[0])/self.tGeotransform[1] + 0.5) - tXoff
  246. tYsize = int((tIntsctLry - self.tGeotransform[3])/self.tGeotransform[5] + 0.5) - tYoff
  247. if tXsize < 1 or tYsize < 1:
  248. return
  249. # Compute source window in pixel coordinates.
  250. sXoff = int((tIntsctUlx - sGeotransform[0]) / sGeotransform[1])
  251. sYoff = int((tIntsctUly - sGeotransform[3]) / sGeotransform[5])
  252. sXsize = int((tIntsctLrx - sGeotransform[0]) / sGeotransform[1] + 0.5) - sXoff
  253. sYsize = int((tIntsctLry - sGeotransform[3]) / sGeotransform[5] + 0.5) - sYoff
  254. if sXsize < 1 or sYsize < 1:
  255. return
  256. for sBandNnum, tBandNum in sTBands.iteritems():
  257. bandData = sDataset.GetRasterBand(sBandNnum).ReadRaster(sXoff, sYoff, sXsize,
  258. sYsize, tXsize, tYsize, gdal.GDT_Byte)
  259. self.tDataset.GetRasterBand(tBandNum).WriteRaster(tXoff, tYoff, tXsize, tYsize, bandData,
  260. tXsize, tYsize, gdal.GDT_Byte)
  261. def _getCorners(self, geoTrans, size):
  262. ulx = geoTrans[0]
  263. uly = geoTrans[3]
  264. lrx = geoTrans[0] + size['cols'] * geoTrans[1]
  265. lry = geoTrans[3] + size['rows'] * geoTrans[5]
  266. return ulx, uly, lrx, lry
  267. def __del__(self):
  268. self.tDataset = None