controller.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. """!
  2. @package animation.controller
  3. @brief Animations management
  4. Classes:
  5. - controller::AnimationController
  6. (C) 2012 by the GRASS Development Team
  7. This program is free software under the GNU General Public License
  8. (>=v2). Read the file COPYING that comes with GRASS for details.
  9. @author Anna Kratochvilova <kratochanna gmail.com>
  10. """
  11. import os
  12. import wx
  13. from core.gcmd import GException, GError, GMessage
  14. import grass.script as grass
  15. from temporal_manager import TemporalManager
  16. from dialogs import InputDialog, EditDialog, AnimationData, ExportDialog
  17. from utils import TemporalMode, Orientation, RenderText
  18. class AnimationController(wx.EvtHandler):
  19. def __init__(self, frame, sliders, animations, mapwindows, providers, bitmapPool):
  20. wx.EvtHandler.__init__(self)
  21. self.mapwindows = mapwindows
  22. self.frame = frame
  23. self.sliders = sliders
  24. self.slider = self.sliders['temporal']
  25. self.animationToolbar = None
  26. self.temporalMode = None
  27. self.animationData = []
  28. self.timer = wx.Timer(self, id = wx.NewId())
  29. self.animations = animations
  30. self.bitmapPool = bitmapPool
  31. self.bitmapProviders = providers
  32. for anim, win, provider in zip(self.animations, self.mapwindows, self.bitmapProviders):
  33. anim.SetCallbackUpdateFrame(lambda index, dataId, win = win, provider = provider : self.UpdateFrame(index, win, provider, dataId))
  34. anim.SetCallbackEndAnimation(lambda index, dataId, win = win, provider = provider: self.UpdateFrameEnd(index, win, provider, dataId))
  35. anim.SetCallbackOrientationChanged(self.OrientationChangedInReverseMode)
  36. for slider in self.sliders.values():
  37. slider.SetCallbackSliderChanging(self.SliderChanging)
  38. slider.SetCallbackSliderChanged(self.SliderChanged)
  39. slider.SetCallbackFrameIndexChanged(self.ChangeFrame)
  40. self.runAfterReleasingSlider = None
  41. self.temporalManager = TemporalManager()
  42. self.Bind(wx.EVT_TIMER, self.OnTimerTick, self.timer)
  43. self.timeTick = 200
  44. def SetAnimationToolbar(self, toolbar):
  45. self.animationToolbar = toolbar
  46. def GetTimeTick(self):
  47. return self._timeTick
  48. def SetTimeTick(self, value):
  49. self._timeTick = value
  50. if self.timer.IsRunning():
  51. self.timer.Stop()
  52. self.timer.Start(self._timeTick)
  53. self.DisableSliderIfNeeded()
  54. timeTick = property(fget = GetTimeTick, fset = SetTimeTick)
  55. def OnTimerTick(self, event):
  56. for anim in self.animations:
  57. anim.Update()
  58. def StartAnimation(self):
  59. # if self.timer.IsRunning():
  60. # self.timer.Stop()
  61. for anim in self.animations:
  62. if self.timer.IsRunning():
  63. anim.NextFrameIndex()
  64. anim.Start()
  65. if not self.timer.IsRunning():
  66. self.timer.Start(self.timeTick)
  67. self.DisableSliderIfNeeded()
  68. def PauseAnimation(self, paused):
  69. if paused:
  70. if self.timer.IsRunning():
  71. self.timer.Stop()
  72. self.DisableSliderIfNeeded()
  73. else:
  74. if not self.timer.IsRunning():
  75. self.timer.Start(self.timeTick)
  76. self.DisableSliderIfNeeded()
  77. for anim in self.animations:
  78. anim.Pause(paused)
  79. def EndAnimation(self):
  80. if self.timer.IsRunning():
  81. self.timer.Stop()
  82. self.DisableSliderIfNeeded()
  83. for anim in self.animations:
  84. anim.Stop()
  85. def UpdateFrameEnd(self, index, win, provider, dataId):
  86. if self.timer.IsRunning():
  87. self.timer.Stop()
  88. self.DisableSliderIfNeeded()
  89. self.animationToolbar.Stop()
  90. self.UpdateFrame(index, win, provider, dataId)
  91. def UpdateFrame(self, index, win, provider, dataId):
  92. bitmap = provider.GetBitmap(dataId)
  93. if dataId is None:
  94. dataId = ''
  95. win.DrawBitmap(bitmap, dataId)
  96. # self.frame.SetStatusText(dataId)
  97. self.slider.UpdateFrame(index)
  98. self.UpdateReloadStatus()
  99. def SliderChanging(self, index):
  100. if self.runAfterReleasingSlider is None:
  101. self.runAfterReleasingSlider = self.timer.IsRunning()
  102. self.PauseAnimation(True)
  103. self.ChangeFrame(index)
  104. def SliderChanged(self):
  105. if self.runAfterReleasingSlider:
  106. self.PauseAnimation(False)
  107. self.runAfterReleasingSlider = None
  108. def ChangeFrame(self, index):
  109. for anim in self.animations:
  110. anim.FrameChangedFromOutside(index)
  111. def DisableSliderIfNeeded(self):
  112. if self.timer.IsRunning() and self._timeTick < 100:
  113. self.slider.EnableSlider(False)
  114. else:
  115. self.slider.EnableSlider(True)
  116. def OrientationChangedInReverseMode(self, mode):
  117. if mode == Orientation.FORWARD:
  118. self.animationToolbar.PlayForward()
  119. elif mode == Orientation.BACKWARD:
  120. self.animationToolbar.PlayBack()
  121. def SetReplayMode(self, mode):
  122. for anim in self.animations:
  123. anim.replayMode = mode
  124. def SetOrientation(self, mode):
  125. for anim in self.animations:
  126. anim.orientation = mode
  127. def SetTemporalMode(self, mode):
  128. self._temporalMode = mode
  129. def GetTemporalMode(self):
  130. return self._temporalMode
  131. temporalMode = property(fget = GetTemporalMode, fset = SetTemporalMode)
  132. def GetTimeGranularity(self):
  133. if self.temporalMode == TemporalMode.TEMPORAL:
  134. return self.temporalManager.GetGranularity()
  135. return None
  136. def EditAnimations(self):
  137. # running = False
  138. # if self.timer.IsRunning():
  139. # running = True
  140. self.EndAnimation()
  141. dlg = EditDialog(parent = self.frame, evalFunction = self.EvaluateInput,
  142. animationData = self.animationData, maxAnimations = len(self.animations))
  143. if dlg.ShowModal() == wx.ID_CANCEL:
  144. dlg.Destroy()
  145. return
  146. self.animationData, self.temporalMode, self.temporalManager = dlg.GetResult()
  147. dlg.Destroy()
  148. self._setAnimations()
  149. def AddAnimation(self):
  150. # check if we can add more animations
  151. found = False
  152. indices = [anim.windowIndex for anim in self.animationData]
  153. for windowIndex in range(len(self.animations)):
  154. if windowIndex not in indices:
  155. found = True
  156. break
  157. if not found:
  158. GMessage(parent = self.frame, message = _("Maximum number of animations is %s.") % len(self.animations))
  159. return
  160. # running = False
  161. # if self.timer.IsRunning():
  162. # running = True
  163. self.EndAnimation()
  164. # self.PauseAnimation(True)
  165. animData = AnimationData()
  166. # number of active animations
  167. animationIndex = len([anim for anim in self.animations if anim.IsActive()])
  168. animData.SetDefaultValues(windowIndex, animationIndex)
  169. dlg = InputDialog(parent = self.frame, mode = 'add', animationData = animData)
  170. if dlg.ShowModal() == wx.ID_CANCEL:
  171. dlg.Destroy()
  172. return
  173. dlg.Destroy()
  174. # check compatibility
  175. if animData.windowIndex in indices:
  176. GMessage(parent = self.frame, message = _("More animations are using one window."
  177. " Please select different window for each animation."))
  178. return
  179. try:
  180. temporalMode, tempManager = self.EvaluateInput(self.animationData + [animData])
  181. except GException, e:
  182. GError(parent = self.frame, message = e.value, showTraceback = False)
  183. return
  184. # if ok, set temporal mode
  185. self.temporalMode = temporalMode
  186. self.temporalManager = tempManager
  187. # add data
  188. windowIndex = animData.windowIndex
  189. self.animationData.append(animData)
  190. self._setAnimations()
  191. def SetAnimations(self, raster = None, strds = None):
  192. """!Set animation data directly.
  193. @param raster list of lists of raster maps or None
  194. @param strds list of strds or None
  195. """
  196. try:
  197. animationData = []
  198. for i in range(len(self.animations)):
  199. if raster is not None and type(raster[i]) == list and raster[i]:
  200. anim = AnimationData()
  201. anim.SetDefaultValues(i, i)
  202. anim.inputMapType = 'rast'
  203. anim.inputData = ','.join(raster[i])
  204. animationData.append(anim)
  205. elif strds is not None and strds[i]:
  206. anim = AnimationData()
  207. anim.SetDefaultValues(i, i)
  208. anim.inputMapType = 'strds'
  209. anim.inputData = strds[i]
  210. animationData.append(anim)
  211. except (GException, ValueError, IOError) as e:
  212. GError(parent = self.frame, message = str(e),
  213. showTraceback = False, caption = _("Invalid input"))
  214. return
  215. try:
  216. temporalMode, tempManager = self.EvaluateInput(animationData)
  217. except GException, e:
  218. GError(parent = self.frame, message = e.value, showTraceback = False)
  219. return
  220. self.animationData = animationData
  221. self.temporalManager = tempManager
  222. self.temporalMode = temporalMode
  223. self._setAnimations()
  224. def _setAnimations(self):
  225. indices = [anim.windowIndex for anim in self.animationData]
  226. self._updateWindows(activeIndices = indices)
  227. if self.temporalMode == TemporalMode.TEMPORAL:
  228. timeLabels, mapNamesDict = self.temporalManager.GetLabelsAndMaps()
  229. else:
  230. timeLabels, mapNamesDict = None, None
  231. self._updateSlider(timeLabels = timeLabels)
  232. self._updateAnimations(activeIndices = indices, mapNamesDict = mapNamesDict)
  233. self._updateRegion()
  234. self._updateBitmapData()
  235. # if running:
  236. # self.PauseAnimation(False)
  237. # # self.StartAnimation()
  238. # else:
  239. self.EndAnimation()
  240. self.UpdateReloadStatus()
  241. def _updateSlider(self, timeLabels = None):
  242. if self.temporalMode == TemporalMode.NONTEMPORAL:
  243. self.frame.SetSlider('nontemporal')
  244. self.slider = self.sliders['nontemporal']
  245. frameCount = len(self.animationData[0].mapData) # should be the same for all
  246. self.slider.SetFrames(frameCount)
  247. elif self.temporalMode == TemporalMode.TEMPORAL:
  248. self.frame.SetSlider('temporal')
  249. self.slider = self.sliders['temporal']
  250. self.slider.SetTemporalType(self.temporalManager.temporalType)
  251. self.slider.SetFrames(timeLabels)
  252. else:
  253. self.frame.SetSlider(None)
  254. self.slider = None
  255. def _updateAnimations(self, activeIndices, mapNamesDict = None):
  256. if self.temporalMode == TemporalMode.NONTEMPORAL:
  257. for i in range(len(self.animations)):
  258. if i not in activeIndices:
  259. self.animations[i].SetActive(False)
  260. continue
  261. anim = [anim for anim in self.animationData if anim.windowIndex == i][0]
  262. self.animations[i].SetFrames(anim.mapData)
  263. self.animations[i].SetActive(True)
  264. else:
  265. for i in range(len(self.animations)):
  266. if i not in activeIndices:
  267. self.animations[i].SetActive(False)
  268. continue
  269. anim = [anim for anim in self.animationData if anim.windowIndex == i][0]
  270. self.animations[i].SetFrames(mapNamesDict[anim.inputData])
  271. self.animations[i].SetActive(True)
  272. def _updateWindows(self, activeIndices):
  273. # add or remove window
  274. for windowIndex in range(len(self.animations)):
  275. if not self.frame.IsWindowShown(windowIndex) and windowIndex in activeIndices:
  276. self.frame.AddWindow(windowIndex)
  277. elif self.frame.IsWindowShown(windowIndex) and windowIndex not in activeIndices:
  278. self.frame.RemoveWindow(windowIndex)
  279. def _updateBitmapData(self):
  280. # unload data:
  281. for prov in self.bitmapProviders:
  282. prov.Unload()
  283. # load data
  284. for animData in self.animationData:
  285. if animData.viewMode == '2d':
  286. self._load2DData(animData)
  287. else:
  288. self._load3DData(animData)
  289. # clear bitmapPool
  290. usedNames = []
  291. for prov in self.bitmapProviders:
  292. names = prov.GetDataNames()
  293. if names:
  294. usedNames.extend(names)
  295. self.bitmapPool.Clear(usedNames)
  296. def _load2DData(self, animationData):
  297. prov = self.bitmapProviders[animationData.windowIndex]
  298. prov.SetData(datasource = animationData.mapData)
  299. self.bitmapProviders[animationData.windowIndex].Load()
  300. def _load3DData(self, animationData):
  301. prov = self.bitmapProviders[animationData.windowIndex]
  302. nviz = animationData.GetNvizCommands()
  303. prov.SetData(datasource = nviz['commands'],
  304. dataNames = animationData.mapData, dataType = 'nviz',
  305. suffix = animationData.nvizParameter,
  306. nvizRegion = nviz['region'])
  307. self.bitmapProviders[animationData.windowIndex].Load()
  308. def EvaluateInput(self, animationData):
  309. stds = 0
  310. maps = 0
  311. mapCount = set()
  312. tempManager = None
  313. windowIndex = []
  314. for anim in animationData:
  315. mapCount.add(len(anim.mapData))
  316. windowIndex.append(anim.windowIndex)
  317. if anim.inputMapType in ('rast', 'vect'):
  318. maps += 1
  319. elif anim.inputMapType in ('strds', 'stvds'):
  320. stds += 1
  321. if maps and stds:
  322. temporalMode = TemporalMode.NONTEMPORAL
  323. elif maps:
  324. temporalMode = TemporalMode.NONTEMPORAL
  325. elif stds:
  326. temporalMode = TemporalMode.TEMPORAL
  327. else:
  328. temporalMode = None
  329. if temporalMode == TemporalMode.NONTEMPORAL:
  330. if len(mapCount) > 1:
  331. raise GException(_("Inconsistent number of maps, please check input data."))
  332. elif temporalMode == TemporalMode.TEMPORAL:
  333. tempManager = TemporalManager()
  334. # these raise GException:
  335. for anim in animationData:
  336. if anim.inputMapType not in ('strds', 'stvds'):
  337. continue
  338. tempManager.AddTimeSeries(anim.inputData, anim.inputMapType)
  339. message = tempManager.EvaluateInputData()
  340. if message:
  341. GMessage(parent = self.frame, message = message)
  342. return temporalMode, tempManager
  343. def Reload(self):
  344. self.EndAnimation()
  345. self._updateRegion()
  346. activeIndices = [anim.windowIndex for anim in self.animationData]
  347. for index in activeIndices:
  348. self.bitmapProviders[index].Load(force = True)
  349. self.EndAnimation()
  350. self.UpdateReloadStatus()
  351. def UpdateReloadStatus(self):
  352. activeIndices = [anim.windowIndex for anim in self.animationData]
  353. for i, win in enumerate(self.mapwindows):
  354. if i in activeIndices and win.IsRescaled():
  355. self.frame.SetStatusText(_("Reload recommended"), 1)
  356. return
  357. self.frame.SetStatusText('', 1)
  358. def _updateRegion(self):
  359. grassRegion = grass.region()
  360. for anim in self.animationData:
  361. if anim.viewMode == '2d':
  362. self.mapwindows[anim.windowIndex].SetRegion(grassRegion)
  363. else:
  364. loadSize = self.bitmapProviders[anim.windowIndex].GetLoadSize() or \
  365. self.mapwindows[anim.windowIndex].GetClientSize()
  366. region = {}
  367. region['cols'], region['rows'] = loadSize
  368. self.mapwindows[anim.windowIndex].SetRegion(region)
  369. def Export(self):
  370. if not self.animationData:
  371. GMessage(parent = self.frame, message = _("No animation to export."))
  372. return
  373. dlg = ExportDialog(self.frame, temporal = self.temporalMode)
  374. if dlg.ShowModal() == wx.ID_OK:
  375. decorations = dlg.GetDecorations()
  376. exportInfo = dlg.GetExportInformation()
  377. dlg.Destroy()
  378. else:
  379. dlg.Destroy()
  380. return
  381. self._export(exportInfo, decorations)
  382. def _export(self, exportInfo, decorations):
  383. size = self.frame.animationPanel.GetSize()
  384. if self.temporalMode == TemporalMode.TEMPORAL:
  385. timeLabels, mapNamesDict = self.temporalManager.GetLabelsAndMaps()
  386. frameCount = len(timeLabels)
  387. else:
  388. frameCount = len(self.animationData[0].mapData) # should be the same for all
  389. animWinSize = []
  390. animWinPos = []
  391. animWinIndex = []
  392. # determine position and sizes of bitmaps
  393. for i, (win, anim) in enumerate(zip(self.mapwindows, self.animations)):
  394. if anim.IsActive():
  395. pos = tuple([pos1 + pos2 for pos1, pos2 in zip(win.GetPosition(), win.GetAdjustedPosition())])
  396. animWinPos.append(pos)
  397. animWinSize.append(win.GetAdjustedSize())
  398. animWinIndex.append(i)
  399. images = []
  400. for frameIndex in range(frameCount):
  401. image = wx.EmptyImage(*size)
  402. image.Replace(0, 0, 0, 255, 255, 255)
  403. # collect bitmaps of all windows and paste them into the one
  404. for i in range(len(animWinSize)):
  405. frameId = self.animations[animWinIndex[i]].GetFrame(frameIndex)
  406. bitmap = self.bitmapProviders[animWinIndex[i]].GetBitmap(frameId)
  407. im = wx.ImageFromBitmap(bitmap)
  408. if im.GetSize() != animWinSize[i]:
  409. im.Rescale(*animWinSize[i])
  410. image.Paste(im, *animWinPos[i])
  411. # paste decorations
  412. for decoration in decorations:
  413. # add image
  414. x = decoration['pos'][0] / 100. * size[0]
  415. y = decoration['pos'][1] / 100. * size[1]
  416. if decoration['name'] == 'image':
  417. decImage = wx.Image(decoration['file'])
  418. elif decoration['name'] == 'time':
  419. timeLabel = timeLabels[frameIndex]
  420. if timeLabel[1]:
  421. text = _("%(from)s %(dash)s %(to)s") % \
  422. {'from': timeLabel[0], 'dash': u"\u2013", 'to': timeLabel[1]}
  423. else:
  424. text = _("%(start)s %(unit)s") % \
  425. {'start': timeLabel[0], 'unit': timeLabel[2]}
  426. decImage = RenderText(text, decoration['font']).ConvertToImage()
  427. elif decoration['name'] == 'text':
  428. text = decoration['text']
  429. decImage = RenderText(text, decoration['font']).ConvertToImage()
  430. image.Paste(decImage, x, y)
  431. images.append(image)
  432. # export
  433. if exportInfo['method'] == 'sequence':
  434. busy = wx.BusyInfo(message = _("Exporting images, please wait..."), parent = self.frame)
  435. wx.Yield()
  436. zeroPadding = len(str(len(images)))
  437. for i, image in enumerate(images):
  438. filename = "%s_%s.%s" % (exportInfo['prefix'], str(i + 1).zfill(zeroPadding),
  439. exportInfo['format']['ext'])
  440. image.SaveFile(os.path.join(exportInfo['directory'], filename), exportInfo['format']['type'])
  441. busy.Destroy()
  442. # image.SaveFile('/home/anna/testy/grass/export/export_%s.png' % frameIndex, wx.BITMAP_TYPE_PNG)
  443. # for anim in self.animationData
  444. #def test():
  445. # import gettext
  446. # gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode = True)
  447. #
  448. # import grass.script as grass
  449. # import wx
  450. # app = wx.PySimpleApp()
  451. # wx.InitAllImageHandlers()
  452. # # app.MainLoop()
  453. #
  454. # bitmaps = {}
  455. # rasters = ['elevation.dem']
  456. # # rasters = ['streams']
  457. # # rasters = grass.read_command("g.mlist", type = 'rast', fs = ',', quiet = True).strip().split(',')
  458. #
  459. # # print nrows, ncols
  460. # size = (300,100)
  461. # newSize, scale = ComputeScale(size)
  462. # # print scale
  463. # LoadRasters(rasters = rasters, bitmaps = bitmaps, scale = scale, size = newSize)
  464. #
  465. # for b in bitmaps.keys():
  466. # bitmaps[b].SaveFile('/home/anna/testy/ctypes/' + b + '.png', wx.BITMAP_TYPE_PNG)
  467. #
  468. #if __name__ == '__main__':
  469. #
  470. # test()