provider.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774
  1. # -*- coding: utf-8 -*-
  2. """!
  3. @package animation.provider
  4. @brief Animation files and bitmaps management
  5. Classes:
  6. - mapwindow::BitmapProvider
  7. - mapwindow::BitmapRenderer
  8. - mapwindow::BitmapComposer
  9. - mapwindow::DictRefCounter
  10. - mapwindow::MapFilesPool
  11. - mapwindow::BitmapPool
  12. - mapwindow::CleanUp
  13. (C) 2013 by the GRASS Development Team
  14. This program is free software under the GNU General Public License
  15. (>=v2). Read the file COPYING that comes with GRASS for details.
  16. @author Anna Petrasova <kratochanna gmail.com>
  17. """
  18. import os
  19. import sys
  20. import wx
  21. import tempfile
  22. from multiprocessing import Process, Queue
  23. if __name__ == '__main__':
  24. sys.path.append(os.path.join(os.environ['GISBASE'], "etc", "gui", "wxpython"))
  25. from core.gcmd import RunCommand, GException
  26. from core.settings import UserSettings
  27. from core.debug import Debug
  28. from core.utils import _, CmdToTuple, autoCropImageFromFile
  29. from animation.utils import HashCmd, HashCmds, GetFileFromCmd, GetFileFromCmds
  30. import grass.script.core as gcore
  31. from grass.pydispatch.signal import Signal
  32. class BitmapProvider:
  33. """!Class for management of image files and bitmaps.
  34. There is one instance of this class in the application.
  35. It handles both 2D and 3D animations.
  36. """
  37. def __init__(self, bitmapPool, mapFilesPool, tempDir,
  38. imageWidth=640, imageHeight=480):
  39. self._bitmapPool = bitmapPool
  40. self._mapFilesPool = mapFilesPool
  41. self.imageWidth = imageWidth # width of the image to render with d.rast or d.vect
  42. self.imageHeight = imageHeight # height of the image to render with d.rast or d.vect
  43. self._tempDir = tempDir
  44. self._uniqueCmds = []
  45. self._cmdsForComposition = []
  46. self._opacities = []
  47. self._cmds3D = []
  48. self._regionFor3D = None
  49. self._renderer = BitmapRenderer(self._mapFilesPool, self._tempDir,
  50. self.imageWidth, self.imageHeight)
  51. self._composer = BitmapComposer(self._tempDir, self._mapFilesPool,
  52. self._bitmapPool, self.imageWidth,
  53. self.imageHeight)
  54. self.renderingStarted = Signal('BitmapProvider.renderingStarted')
  55. self.compositionStarted = Signal('BitmapProvider.compositionStarted')
  56. self.renderingContinues = Signal('BitmapProvider.renderingContinues')
  57. self.compositionContinues = Signal('BitmapProvider.compositionContinues')
  58. self.renderingFinished = Signal('BitmapProvider.renderingFinished')
  59. self.compositionFinished = Signal('BitmapProvider.compositionFinished')
  60. self.mapsLoaded = Signal('BitmapProvider.mapsLoaded')
  61. self._renderer.renderingContinues.connect(self.renderingContinues)
  62. self._composer.compositionContinues.connect(self.compositionContinues)
  63. def SetCmds(self, cmdsForComposition, opacities):
  64. """!Sets commands to be rendered with opacity levels.
  65. Applies to 2D mode.
  66. @param cmdsForComposition list of lists of command lists
  67. [[['d.rast', 'map=elev_2001'], ['d.vect', 'map=points']], # g.pnmcomp
  68. [['d.rast', 'map=elev_2002'], ['d.vect', 'map=points']],
  69. ...]
  70. @param opacities list of opacity values
  71. """
  72. Debug.msg(2, "BitmapProvider.SetCmds: {} lists".format(len(cmdsForComposition)))
  73. self._cmdsForComposition.extend(cmdsForComposition)
  74. self._uniqueCmds = self._getUniqueCmds()
  75. self._opacities.extend(opacities)
  76. def SetCmds3D(self, cmds, region):
  77. """!Sets commands for 3D rendering.
  78. @param cmds list of commands m.nviz.image (cmd as a list)
  79. @param region for 3D rendering
  80. """
  81. Debug.msg(2, "BitmapProvider.SetCmds3D: {} commands".format(len(cmds)))
  82. self._cmds3D = cmds
  83. self._regionFor3D = region
  84. def _getUniqueCmds(self):
  85. """!Returns list of unique commands."""
  86. unique = set()
  87. for cmdList in self._cmdsForComposition:
  88. for cmd in cmdList:
  89. unique.add(tuple(cmd))
  90. return list(unique)
  91. def Unload(self):
  92. """!Unloads currently loaded data.
  93. Needs to be called before setting new data.
  94. """
  95. Debug.msg(2, "BitmapProvider.Unload")
  96. if self._cmdsForComposition:
  97. for cmd in self._uniqueCmds:
  98. del self._mapFilesPool[HashCmd(cmd)]
  99. for cmdList in self._cmdsForComposition:
  100. del self._bitmapPool[HashCmds(cmdList)]
  101. self._uniqueCmds = []
  102. self._cmdsForComposition = []
  103. self._opacities = []
  104. if self._cmds3D:
  105. self._cmds3D = []
  106. self._regionFor3D = None
  107. def _dryRender(self, uniqueCmds, force):
  108. """!Determines how many files will be rendered.
  109. @param uniqueCmds list of commands which are to be rendered
  110. @param force if forced rerendering
  111. """
  112. count = 0
  113. for cmd in uniqueCmds:
  114. filename = GetFileFromCmd(self._tempDir, cmd)
  115. if not force and os.path.exists(filename) and \
  116. self._mapFilesPool.GetSize(HashCmd(cmd)) == (self.imageWidth, self.imageHeight):
  117. continue
  118. count += 1
  119. Debug.msg(3, "BitmapProvider._dryRender: {} files to be rendered".format(count))
  120. return count
  121. def _dryCompose(self, cmdLists, force):
  122. """!Determines how many lists of (commands) files
  123. will be composed (with g.pnmcomp).
  124. @param cmdLists list of commands lists which are to be composed
  125. @param force if forced rerendering
  126. """
  127. count = 0
  128. for cmdList in cmdLists:
  129. if not force and HashCmds(cmdList) in self._bitmapPool and \
  130. self._bitmapPool[HashCmds(cmdList)].GetSize() == (self.imageWidth,
  131. self.imageHeight):
  132. continue
  133. count += 1
  134. Debug.msg(2, "BitmapProvider._dryCompose: {} files to be composed".format(count))
  135. return count
  136. def Load(self, force=False, bgcolor=(255, 255, 255), nprocs=4):
  137. """!Loads data, both 2D and 3D. In case of 2D, it creates composites,
  138. even when there is only 1 layer to compose (to be changed for speedup)
  139. @param force if True reload all data, otherwise only missing data
  140. @param bgcolor background color as a tuple of 3 values 0 to 255
  141. @param nprocs number of procs to be used for rendering
  142. """
  143. Debug.msg(2, "BitmapProvider.Load: "
  144. "force={}, bgcolor={}, nprocs={}".format(force, bgcolor, nprocs))
  145. cmds = []
  146. if self._uniqueCmds:
  147. cmds.extend(self._uniqueCmds)
  148. if self._cmds3D:
  149. cmds.extend(self._cmds3D)
  150. count = self._dryRender(cmds, force=force)
  151. self.renderingStarted.emit(count=count)
  152. # create no data bitmap
  153. if None not in self._bitmapPool or force:
  154. self._bitmapPool[None] = createNoDataBitmap(self.imageWidth, self.imageHeight)
  155. ok = self._renderer.Render(cmds, regionFor3D=self._regionFor3D,
  156. bgcolor=bgcolor, force=force, nprocs=nprocs)
  157. self.renderingFinished.emit()
  158. if not ok:
  159. self.mapsLoaded.emit() # what to do here?
  160. return
  161. if self._cmdsForComposition:
  162. count = self._dryCompose(self._cmdsForComposition, force=force)
  163. self.compositionStarted.emit(count=count)
  164. self._composer.Compose(self._cmdsForComposition, self._opacities,
  165. bgcolor=bgcolor, force=force, nprocs=nprocs)
  166. self.compositionFinished.emit()
  167. if self._cmds3D:
  168. for cmd in self._cmds3D:
  169. self._bitmapPool[HashCmd(cmd)] = \
  170. wx.Bitmap(GetFileFromCmd(self._tempDir, cmd))
  171. self.mapsLoaded.emit()
  172. def RequestStopRendering(self):
  173. """!Requests to stop rendering/composition"""
  174. Debug.msg(2, "BitmapProvider.RequestStopRendering")
  175. self._renderer.RequestStopRendering()
  176. self._composer.RequestStopComposing()
  177. def GetBitmap(self, dataId):
  178. """!Returns bitmap with given key
  179. or 'no data' bitmap if no such key exists.
  180. @param dataId name of bitmap
  181. """
  182. try:
  183. bitmap = self._bitmapPool[dataId]
  184. except KeyError:
  185. bitmap = self._bitmapPool[None]
  186. return bitmap
  187. def WindowSizeChanged(self, width, height):
  188. """!Sets size when size of related window changes."""
  189. Debug.msg(5, "BitmapProvider.WindowSizeChanged: w={}, h={}".format(width, height))
  190. self.imageWidth, self.imageHeight = width, height
  191. self._composer.imageWidth = self._renderer.imageWidth = width
  192. self._composer.imageHeight = self._renderer.imageHeight = height
  193. def LoadOverlay(self, cmd):
  194. """!Creates raster legend with d.legend
  195. @param cmd d.legend command as a list
  196. @return bitmap with legend
  197. """
  198. Debug.msg(5, "BitmapProvider.LoadOverlay: cmd={}".format(cmd))
  199. fileHandler, filename = tempfile.mkstemp(suffix=".png")
  200. os.close(fileHandler)
  201. # Set the environment variables for this process
  202. _setEnvironment(self.imageWidth, self.imageHeight, filename,
  203. transparent=True, bgcolor=(0, 0, 0))
  204. Debug.msg(1, "Render raster legend " + str(filename))
  205. cmdTuple = CmdToTuple(cmd)
  206. returncode, stdout, messages = read2_command(cmdTuple[0], **cmdTuple[1])
  207. if returncode == 0:
  208. return wx.BitmapFromImage(autoCropImageFromFile(filename))
  209. else:
  210. os.remove(filename)
  211. raise GException(messages)
  212. class BitmapRenderer:
  213. """!Class which renderes 2D and 3D images to files."""
  214. def __init__(self, mapFilesPool, tempDir,
  215. imageWidth, imageHeight):
  216. self._mapFilesPool = mapFilesPool
  217. self._tempDir = tempDir
  218. self.imageWidth = imageWidth
  219. self.imageHeight = imageHeight
  220. self.renderingContinues = Signal('BitmapRenderer.renderingContinues')
  221. self._stopRendering = False
  222. self._isRendering = False
  223. def Render(self, cmdList, regionFor3D, bgcolor, force, nprocs):
  224. """!Renders all maps and stores files.
  225. @param cmdList list of rendering commands to run
  226. @param regionFor3D region for setting 3D view
  227. @param bgcolor background color as a tuple of 3 values 0 to 255
  228. @param force if True reload all data, otherwise only missing data
  229. @param nprocs number of procs to be used for rendering
  230. """
  231. Debug.msg(3, "BitmapRenderer.Render")
  232. count = 0
  233. # Variables for parallel rendering
  234. proc_count = 0
  235. proc_list = []
  236. queue_list = []
  237. cmd_list = []
  238. filteredCmdList = []
  239. for cmd in cmdList:
  240. filename = GetFileFromCmd(self._tempDir, cmd)
  241. if not force and os.path.exists(filename) and \
  242. self._mapFilesPool.GetSize(HashCmd(cmd)) == (self.imageWidth, self.imageHeight):
  243. # for reference counting
  244. self._mapFilesPool[HashCmd(cmd)] = filename
  245. continue
  246. filteredCmdList.append(cmd)
  247. mapNum = len(filteredCmdList)
  248. stopped = False
  249. self._isRendering = True
  250. for cmd in filteredCmdList:
  251. count += 1
  252. # Queue object for interprocess communication
  253. q = Queue()
  254. # The separate render process
  255. if cmd[0] == 'm.nviz.image':
  256. p = Process(target=self.RenderProcess3D, args=(cmd, regionFor3D, bgcolor, q))
  257. else:
  258. p = Process(target=RenderProcess2D,
  259. args=(self.imageWidth, self.imageHeight, self._tempDir, cmd, bgcolor, q))
  260. p.start()
  261. queue_list.append(q)
  262. proc_list.append(p)
  263. cmd_list.append(cmd)
  264. proc_count += 1
  265. # Wait for all running processes and read/store the created images
  266. if proc_count == nprocs or count == mapNum:
  267. for i in range(len(cmd_list)):
  268. proc_list[i].join()
  269. filename = queue_list[i].get()
  270. self._mapFilesPool[HashCmd(cmd_list[i])] = filename
  271. self._mapFilesPool.SetSize(HashCmd(cmd_list[i]),
  272. (self.imageWidth, self.imageHeight))
  273. proc_count = 0
  274. proc_list = []
  275. queue_list = []
  276. cmd_list = []
  277. self.renderingContinues.emit(current=count, text=_("Rendering map layers"))
  278. if self._stopRendering:
  279. self._stopRendering = False
  280. stopped = True
  281. break
  282. self._isRendering = False
  283. return not stopped
  284. def RequestStopRendering(self):
  285. """!Requests to stop rendering."""
  286. if self._isRendering:
  287. self._stopRendering = True
  288. class BitmapComposer:
  289. """!Class which handles the composition of image files with g.pnmcomp."""
  290. def __init__(self, tempDir, mapFilesPool, bitmapPool,
  291. imageWidth, imageHeight):
  292. self._mapFilesPool = mapFilesPool
  293. self._bitmapPool = bitmapPool
  294. self._tempDir = tempDir
  295. self.imageWidth = imageWidth
  296. self.imageHeight = imageHeight
  297. self.compositionContinues = Signal('BitmapComposer.composingContinues')
  298. self._stopComposing = False
  299. self._isComposing = False
  300. def Compose(self, cmdLists, opacityList, bgcolor, force, nprocs):
  301. """!Performs the composition of ppm/pgm files.
  302. @param cmdLisst lists of rendering commands lists to compose
  303. @param opacityList list of lists of opacity values
  304. @param bgcolor background color as a tuple of 3 values 0 to 255
  305. @param force if True reload all data, otherwise only missing data
  306. @param nprocs number of procs to be used for rendering
  307. """
  308. Debug.msg(3, "BitmapComposer.Compose")
  309. count = 0
  310. # Variables for parallel rendering
  311. proc_count = 0
  312. proc_list = []
  313. queue_list = []
  314. cmd_lists = []
  315. filteredCmdLists = []
  316. for cmdList in cmdLists:
  317. if not force and HashCmds(cmdList) in self._bitmapPool and \
  318. self._bitmapPool[HashCmds(cmdList)].GetSize() == (self.imageWidth,
  319. self.imageHeight):
  320. # TODO: find a better way than to assign the same to increase the reference
  321. self._bitmapPool[HashCmds(cmdList)] = self._bitmapPool[HashCmds(cmdList)]
  322. continue
  323. filteredCmdLists.append(cmdList)
  324. num = len(filteredCmdLists)
  325. self._isComposing = True
  326. for cmdList in filteredCmdLists:
  327. count += 1
  328. # Queue object for interprocess communication
  329. q = Queue()
  330. # The separate render process
  331. p = Process(target=CompositeProcess,
  332. args=(self.imageWidth, self.imageHeight, self._tempDir,
  333. cmdList, opacityList, bgcolor, q))
  334. p.start()
  335. queue_list.append(q)
  336. proc_list.append(p)
  337. cmd_lists.append(cmdList)
  338. proc_count += 1
  339. # Wait for all running processes and read/store the created images
  340. if proc_count == nprocs or count == num:
  341. for i in range(len(cmd_lists)):
  342. proc_list[i].join()
  343. filename = queue_list[i].get()
  344. if filename is None:
  345. self._bitmapPool[HashCmds(cmd_lists[i])] = \
  346. createNoDataBitmap(self.imageWidth, self.imageHeight,
  347. text="Failed to render")
  348. else:
  349. self._bitmapPool[HashCmds(cmd_lists[i])] = \
  350. wx.BitmapFromImage(wx.Image(filename))
  351. os.remove(filename)
  352. proc_count = 0
  353. proc_list = []
  354. queue_list = []
  355. cmd_lists = []
  356. self.compositionContinues.emit(current=count, text=_("Overlaying map layers"))
  357. if self._stopComposing:
  358. self._stopComposing = False
  359. break
  360. self._isComposing = False
  361. def RequestStopComposing(self):
  362. """!Requests to stop the composition."""
  363. if self._isComposing:
  364. self._stopComposing = True
  365. def RenderProcess2D(imageWidth, imageHeight, tempDir, cmd, bgcolor, fileQueue):
  366. """!Render raster or vector files as ppm image and write the
  367. resulting ppm filename in the provided file queue
  368. @param imageWidth image width
  369. @param imageHeight image height
  370. @param tempDir directory for rendering
  371. @param cmd d.rast/d.vect command as a list
  372. @param bgcolor background color as a tuple of 3 values 0 to 255
  373. @param fileQueue the inter process communication queue
  374. storing the file name of the image
  375. """
  376. filename = GetFileFromCmd(tempDir, cmd)
  377. transparency = True
  378. # Set the environment variables for this process
  379. _setEnvironment(imageWidth, imageHeight, filename,
  380. transparent=transparency, bgcolor=bgcolor)
  381. cmdTuple = CmdToTuple(cmd)
  382. returncode, stdout, messages = read2_command(cmdTuple[0], **cmdTuple[1])
  383. if returncode != 0:
  384. gcore.warning("Rendering failed:\n" + messages)
  385. fileQueue.put(None)
  386. os.remove(filename)
  387. return
  388. fileQueue.put(filename)
  389. def RenderProcess3D(imageWidth, imageHeight, tempDir, cmd, region, bgcolor, fileQueue):
  390. """!Renders image with m.nviz.image and writes the
  391. resulting ppm filename in the provided file queue
  392. @param imageWidth image width
  393. @param imageHeight image height
  394. @param tempDir directory for rendering
  395. @param cmd m.nviz.image command as a list
  396. @param bgcolor background color as a tuple of 3 values 0 to 255
  397. @param fileQueue the inter process communication queue
  398. storing the file name of the image
  399. """
  400. filename = GetFileFromCmd(tempDir, cmd)
  401. os.environ['GRASS_REGION'] = gcore.region_env(**region)
  402. Debug.msg(1, "Render image to file " + str(filename))
  403. cmdTuple = CmdToTuple(cmd)
  404. cmdTuple[1]['output'] = os.path.splitext(filename)[0]
  405. # set size
  406. cmdTuple[1]['size'] = '%d,%d' % (imageWidth, imageHeight)
  407. # set format
  408. cmdTuple[1]['format'] = 'ppm'
  409. cmdTuple[1]['bgcolor'] = bgcolor = ':'.join([str(part) for part in bgcolor])
  410. returncode, stdout, messages = read2_command(cmdTuple[0], **cmdTuple[1])
  411. if returncode != 0:
  412. gcore.warning("Rendering failed:\n" + messages)
  413. fileQueue.put(None)
  414. os.environ.pop('GRASS_REGION')
  415. return
  416. os.environ.pop('GRASS_REGION')
  417. fileQueue.put(filename)
  418. def CompositeProcess(imageWidth, imageHeight, tempDir, cmdList, opacities, bgcolor, fileQueue):
  419. """!Performs the composition of image ppm files and writes the
  420. resulting ppm filename in the provided file queue
  421. @param imageWidth image width
  422. @param imageHeight image height
  423. @param tempDir directory for rendering
  424. @param cmdList list of d.rast/d.vect commands
  425. @param opacities list of opacities
  426. @param bgcolor background color as a tuple of 3 values 0 to 255
  427. @param fileQueue the inter process communication queue
  428. storing the file name of the image
  429. """
  430. maps = []
  431. masks = []
  432. for cmd in cmdList:
  433. maps.append(GetFileFromCmd(tempDir, cmd))
  434. masks.append(GetFileFromCmd(tempDir, cmd, 'pgm'))
  435. filename = GetFileFromCmds(tempDir, cmdList)
  436. # Set the environment variables for this process
  437. _setEnvironment(imageWidth, imageHeight, filename,
  438. transparent=False, bgcolor=bgcolor)
  439. opacities = [str(op) for op in opacities]
  440. bgcolor = ':'.join([str(part) for part in bgcolor])
  441. returncode, stdout, messages = read2_command('g.pnmcomp',
  442. overwrite=True,
  443. input='%s' % ",".join(reversed(maps)),
  444. mask='%s' % ",".join(reversed(masks)),
  445. opacity='%s' % ",".join(reversed(opacities)),
  446. bgcolor=bgcolor,
  447. width=imageWidth,
  448. height=imageHeight,
  449. output=filename)
  450. if returncode != 0:
  451. gcore.warning("Rendering composite failed:\n" + messages)
  452. fileQueue.put(None)
  453. os.remove(filename)
  454. return
  455. fileQueue.put(filename)
  456. class DictRefCounter:
  457. """!Base class storing map files/bitmaps (emulates dictionary).
  458. Counts the references to know which files/bitmaps to delete."""
  459. def __init__(self):
  460. self.dictionary = {}
  461. self.referenceCount = {}
  462. def __getitem__(self, key):
  463. return self.dictionary[key]
  464. def __setitem__(self, key, value):
  465. self.dictionary[key] = value
  466. if key not in self.referenceCount:
  467. self.referenceCount[key] = 1
  468. else:
  469. self.referenceCount[key] += 1
  470. Debug.msg(5, 'DictRefCounter.__setitem__: +1 for key {}'.format(key))
  471. def __contains__(self, key):
  472. return key in self.dictionary
  473. def __delitem__(self, key):
  474. self.referenceCount[key] -= 1
  475. Debug.msg(5, 'DictRefCounter.__delitem__: -1 for key {}'.format(key))
  476. def keys(self):
  477. return self.dictionary.keys()
  478. def Clear(self):
  479. """!Clears items which are not needed any more."""
  480. Debug.msg(4, 'DictRefCounter.Clear')
  481. for key in self.dictionary.keys():
  482. if key is not None:
  483. if self.referenceCount[key] <= 0:
  484. del self.dictionary[key]
  485. del self.referenceCount[key]
  486. class MapFilesPool(DictRefCounter):
  487. """!Stores rendered images as files."""
  488. def __init__(self):
  489. DictRefCounter.__init__(self)
  490. self.size = {}
  491. def SetSize(self, key, size):
  492. self.size[key] = size
  493. def GetSize(self, key):
  494. return self.size[key]
  495. def Clear(self):
  496. """!Removes files which are not needed anymore.
  497. Removes both ppm and pgm.
  498. """
  499. Debug.msg(4, 'MapFilesPool.Clear')
  500. for key in self.dictionary.keys():
  501. if self.referenceCount[key] <= 0:
  502. name, ext = os.path.splitext(self.dictionary[key])
  503. os.remove(self.dictionary[key])
  504. if ext == '.ppm':
  505. os.remove(name + '.pgm')
  506. del self.dictionary[key]
  507. del self.referenceCount[key]
  508. del self.size[key]
  509. class BitmapPool(DictRefCounter):
  510. """!Class storing bitmaps (emulates dictionary)"""
  511. def __init__(self):
  512. DictRefCounter.__init__(self)
  513. class CleanUp:
  514. """!Responsible for cleaning up the files."""
  515. def __init__(self, tempDir):
  516. self._tempDir = tempDir
  517. def __call__(self):
  518. import shutil
  519. if os.path.exists(self._tempDir):
  520. try:
  521. shutil.rmtree(self._tempDir)
  522. Debug.msg(5, 'CleanUp: removed directory {}'.format(self._tempDir))
  523. except OSError:
  524. gcore.warning(_("Directory {} not removed.").format(self._tempDir))
  525. def _setEnvironment(width, height, filename, transparent, bgcolor):
  526. """!Sets environmental variables for 2D rendering.
  527. @param width rendering width
  528. @param height rendering height
  529. @param filename file name
  530. @param transparent use transparency
  531. @param bgcolor background color as a tuple of 3 values 0 to 255
  532. """
  533. Debug.msg(5, "_setEnvironment: width={}, height={}, "
  534. "filename={}, transparent={}, bgcolor={}".format(width, height, filename,
  535. transparent, bgcolor))
  536. os.environ['GRASS_WIDTH'] = str(width)
  537. os.environ['GRASS_HEIGHT'] = str(height)
  538. driver = UserSettings.Get(group='display', key='driver', subkey='type')
  539. os.environ['GRASS_RENDER_IMMEDIATE'] = driver
  540. os.environ['GRASS_BACKGROUNDCOLOR'] = '{:02x}{:02x}{:02x}'.format(*bgcolor)
  541. os.environ['GRASS_TRUECOLOR'] = "TRUE"
  542. if transparent:
  543. os.environ['GRASS_TRANSPARENT'] = "TRUE"
  544. else:
  545. os.environ['GRASS_TRANSPARENT'] = "FALSE"
  546. os.environ['GRASS_PNGFILE'] = str(filename)
  547. def createNoDataBitmap(imageWidth, imageHeight, text="No data"):
  548. """!Creates 'no data' bitmap.
  549. Used when requested bitmap is not available (loading data was not successful) or
  550. we want to show 'no data' bitmap.
  551. @param imageWidth image width
  552. @param imageHeight image height
  553. """
  554. Debug.msg(4, "createNoDataBitmap: w={}, h={}, text={}".format(imageWidth,
  555. imageHeight, text))
  556. bitmap = wx.EmptyBitmap(imageWidth, imageHeight)
  557. dc = wx.MemoryDC()
  558. dc.SelectObject(bitmap)
  559. dc.Clear()
  560. text = _(text)
  561. dc.SetFont(wx.Font(pointSize=40, family=wx.FONTFAMILY_SCRIPT,
  562. style=wx.FONTSTYLE_NORMAL, weight=wx.FONTWEIGHT_BOLD))
  563. tw, th = dc.GetTextExtent(text)
  564. dc.DrawText(text, (imageWidth - tw) / 2, (imageHeight - th) / 2)
  565. dc.SelectObject(wx.NullBitmap)
  566. return bitmap
  567. def read2_command(*args, **kwargs):
  568. kwargs['stdout'] = gcore.PIPE
  569. kwargs['stderr'] = gcore.PIPE
  570. ps = gcore.start_command(*args, **kwargs)
  571. stdout, stderr = ps.communicate()
  572. return ps.returncode, stdout, stderr
  573. def test():
  574. import shutil
  575. from core.layerlist import LayerList, Layer
  576. from animation.data import AnimLayer
  577. from animation.utils import layerListToCmdsMatrix
  578. layerList = LayerList()
  579. layer = AnimLayer()
  580. layer.mapType = 'strds'
  581. layer.name = 'JR'
  582. layer.cmd = ['d.rast', 'map=elev_2007_1m']
  583. layerList.AddLayer(layer)
  584. layer = Layer()
  585. layer.mapType = 'vect'
  586. layer.name = 'buildings_2009_approx'
  587. layer.cmd = ['d.vect', 'map=buildings_2009_approx',
  588. 'color=grey']
  589. layer.opacity = 50
  590. layerList.AddLayer(layer)
  591. bPool = BitmapPool()
  592. mapFilesPool = MapFilesPool()
  593. tempDir = '/tmp/test'
  594. if os.path.exists(tempDir):
  595. shutil.rmtree(tempDir)
  596. os.mkdir(tempDir)
  597. # comment this line to keep the directory after prgm ends
  598. # cleanUp = CleanUp(tempDir)
  599. # import atexit
  600. # atexit.register(cleanUp)
  601. prov = BitmapProvider(bPool, mapFilesPool, tempDir,
  602. imageWidth=640, imageHeight=480)
  603. prov.renderingStarted.connect(
  604. lambda count: sys.stdout.write("Total number of maps: {}\n".format(count)))
  605. prov.renderingContinues.connect(
  606. lambda current, text: sys.stdout.write("Current number: {}\n".format(current)))
  607. prov.compositionStarted.connect(
  608. lambda count: sys.stdout.write("Composition: total number of maps: {}\n".format(count)))
  609. prov.compositionContinues.connect(
  610. lambda current, text: sys.stdout.write("Composition: Current number: {}\n".format(current)))
  611. prov.mapsLoaded.connect(
  612. lambda: sys.stdout.write("Maps loading finished\n"))
  613. cmdMatrix = layerListToCmdsMatrix(layerList)
  614. prov.SetCmds(cmdMatrix, [l.opacity for l in layerList])
  615. app = wx.App()
  616. prov.Load(bgcolor=(13, 156, 230), nprocs=4)
  617. for key in bPool.keys():
  618. if key is not None:
  619. bPool[key].SaveFile(os.path.join(tempDir, key + '.png'), wx.BITMAP_TYPE_PNG)
  620. # prov.Unload()
  621. # prov.SetCmds(cmdMatrix, [l.opacity for l in layerList])
  622. # prov.Load(bgcolor=(13, 156, 230))
  623. # prov.Unload()
  624. # newList = LayerList()
  625. # prov.SetCmds(layerListToCmdsMatrix(newList), [l.opacity for l in newList])
  626. # prov.Load()
  627. # prov.Unload()
  628. # mapFilesPool.Clear()
  629. # bPool.Clear()
  630. # print bPool.keys(), mapFilesPool.keys()
  631. if __name__ == '__main__':
  632. test()