ws.py 11 KB

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