main.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. """
  2. @package mapdisp.main
  3. @brief Start Map Display as standalone application
  4. Classes:
  5. - mapdisp::DMonMap
  6. - mapdisp::Layer
  7. - mapdisp::LayerList
  8. - mapdisp::DMonGrassInterface
  9. - mapdisp::DMonFrame
  10. - mapdisp::MapApp
  11. Usage:
  12. python mapdisp/main.py monitor-identifier /path/to/map/file /path/to/command/file /path/to/env/file
  13. (C) 2006-2015 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 Michael Barton
  17. @author Jachym Cepicky
  18. @author Martin Landa <landa.martin gmail.com>
  19. @author Vaclav Petras <wenzeslaus gmail.com> (MapFrameBase)
  20. @author Anna Kratochvilova <kratochanna gmail.com> (MapFrameBase)
  21. """
  22. import os
  23. import sys
  24. import time
  25. import shutil
  26. import fileinput
  27. from grass.script.setup import set_gui_path
  28. set_gui_path()
  29. from core import globalvar
  30. import wx
  31. from core import utils
  32. from core.giface import StandaloneGrassInterface
  33. from core.gcmd import RunCommand
  34. from core.render import Map, MapLayer, Overlay, RenderMapMgr
  35. from core.utils import _
  36. from mapdisp.frame import MapFrame
  37. from core.debug import Debug
  38. from core.settings import UserSettings
  39. from grass.script.utils import try_remove
  40. from grass.script import core as grass
  41. from grass.script.task import cmdtuple_to_list
  42. from grass.pydispatch.signal import Signal
  43. # for standalone app
  44. monFile = {'cmd': None,
  45. 'map': None,
  46. 'env': None,
  47. }
  48. monName = None
  49. monSize = list(globalvar.MAP_WINDOW_SIZE)
  50. monDecor = False
  51. class DMonMap(Map):
  52. def __init__(self, giface, cmdfile=None, mapfile=None):
  53. """Map composition (stack of map layers and overlays)
  54. :param cmdline: full path to the cmd file (defined by d.mon)
  55. :param mapfile: full path to the map file (defined by d.mon)
  56. """
  57. Map.__init__(self)
  58. self._giface = giface
  59. # environment settings
  60. self.env = dict()
  61. self.cmdfile = cmdfile
  62. # list of layers for rendering added from cmd file
  63. # TODO temporary solution, layer managment by different tools in GRASS
  64. # should be resovled
  65. self.ownedLayers = []
  66. self.oldOverlays = []
  67. if mapfile:
  68. self.mapfileCmd = mapfile
  69. self.maskfileCmd = os.path.splitext(mapfile)[0] + '.pgm'
  70. # generated file for g.pnmcomp output for rendering the map
  71. self.mapfile = monFile['map']
  72. if os.path.splitext(self.mapfile)[1] != '.ppm':
  73. self.mapfile += '.ppm'
  74. # signal sent when d.out.file/d.to.rast appears in cmd file, attribute
  75. # is cmd
  76. self.saveToFile = Signal('DMonMap.saveToFile')
  77. self.dToRast = Signal('DMonMap.dToRast')
  78. # signal sent when d.what.rast/vect appears in cmd file, attribute is
  79. # cmd
  80. self.query = Signal('DMonMap.query')
  81. self.renderMgr = RenderMapMgr(self)
  82. def GetLayersFromCmdFile(self):
  83. """Get list of map layers from cmdfile
  84. """
  85. if not self.cmdfile:
  86. return
  87. nlayers = 0
  88. try:
  89. fd = open(self.cmdfile, 'r')
  90. lines = fd.readlines()
  91. fd.close()
  92. # detect d.out.file, delete the line from the cmd file and export
  93. # graphics
  94. if len(lines) > 0:
  95. if lines[-1].startswith('d.out.file') or \
  96. lines[-1].startswith('d.to.rast'):
  97. dCmd = lines[-1].strip()
  98. fd = open(self.cmdfile, 'w')
  99. fd.writelines(lines[:-1])
  100. fd.close()
  101. if lines[-1].startswith('d.out.file'):
  102. self.saveToFile.emit(cmd=utils.split(dCmd))
  103. else:
  104. self.dToRast.emit(cmd=utils.split(dCmd))
  105. return
  106. if lines[-1].startswith('d.what'):
  107. dWhatCmd = lines[-1].strip()
  108. fd = open(self.cmdfile, 'w')
  109. fd.writelines(lines[:-1])
  110. fd.close()
  111. if '=' in utils.split(dWhatCmd)[1]:
  112. maps = utils.split(dWhatCmd)[1].split('=')[
  113. 1].split(',')
  114. else:
  115. maps = utils.split(dWhatCmd)[1].split(',')
  116. self.query.emit(ltype=utils.split(dWhatCmd)[
  117. 0].split('.')[-1], maps=maps)
  118. return
  119. existingLayers = self.GetListOfLayers()
  120. # holds new rendreing order for every layer in existingLayers
  121. layersOrder = [-1] * len(existingLayers)
  122. # next number in rendering order
  123. next_layer = 0
  124. mapFile = None
  125. render_env = dict()
  126. for line in lines:
  127. if line.startswith('#'):
  128. if 'GRASS_RENDER_FILE' in line:
  129. mapFile = line.split('=', 1)[1].strip()
  130. try:
  131. k, v = line[2:].strip().split('=', 1)
  132. except:
  133. pass
  134. render_env[k] = v
  135. continue
  136. cmd = utils.split(line.strip())
  137. ltype = None
  138. try:
  139. ltype = utils.command2ltype[cmd[0]]
  140. except KeyError:
  141. grass.warning(_("Unsupported command %s.") % cmd[0])
  142. continue
  143. name = utils.GetLayerNameFromCmd(cmd, fullyQualified=True,
  144. layerType=ltype)[0]
  145. args = {}
  146. if ltype in ('barscale', 'rastleg', 'northarrow', 'text'):
  147. # TODO: this is still not optimal
  148. # it is there to prevent adding the same overlay multiple times
  149. if cmd in self.oldOverlays:
  150. continue
  151. if ltype == 'rastleg':
  152. self._giface.GetMapDisplay().AddLegend(cmd=cmd)
  153. elif ltype == 'barscale':
  154. self._giface.GetMapDisplay().AddBarscale(cmd=cmd)
  155. elif ltype == 'northarrow':
  156. self._giface.GetMapDisplay().AddArrow(cmd=cmd)
  157. elif ltype == 'text':
  158. self._giface.GetMapDisplay().AddDtext(cmd=cmd)
  159. self.oldOverlays.append(cmd)
  160. continue
  161. classLayer = MapLayer
  162. args['ltype'] = ltype
  163. mapLayer = classLayer(name=name,
  164. cmd=cmd,
  165. Map=None,
  166. hidden=True,
  167. render=False,
  168. mapfile=mapFile,
  169. **args)
  170. mapLayer.GetRenderMgr().updateProgress.connect(self.GetRenderMgr().ReportProgress)
  171. if render_env:
  172. mapLayer.GetRenderMgr().UpdateRenderEnv(render_env)
  173. render_env = dict()
  174. exists = False
  175. for i, layer in enumerate(existingLayers):
  176. if layer.GetCmd(
  177. string=True) == mapLayer.GetCmd(
  178. string=True):
  179. exists = True
  180. if layersOrder[i] == -1:
  181. layersOrder[i] = next_layer
  182. next_layer += 1
  183. # layer must be put higher in render order (same cmd was insered more times)
  184. # TODO delete rendurant cmds from cmd file?
  185. else:
  186. for j, l_order in enumerate(layersOrder):
  187. if l_order > layersOrder[i]:
  188. layersOrder[j] -= 1
  189. layersOrder[i] = next_layer - 1
  190. break
  191. if exists:
  192. continue
  193. newLayer = self._addLayer(mapLayer)
  194. existingLayers.append(newLayer)
  195. self.ownedLayers.append(newLayer)
  196. layersOrder.append(next_layer)
  197. next_layer += 1
  198. nlayers += 1
  199. reorderedLayers = [-1] * next_layer
  200. for i, layer in enumerate(existingLayers):
  201. # owned layer was not found in cmd file -> is deleted
  202. if layersOrder[i] == -1 and layer in self.ownedLayers:
  203. self.ownedLayers.remove(layer)
  204. self.DeleteLayer(layer)
  205. # other layer e. g. added by wx.vnet are added to the top
  206. elif layersOrder[i] == -1 and layer not in self.ownedLayers:
  207. reorderedLayers.append(layer)
  208. # owned layer found in cmd file is added into proper rendering
  209. # position
  210. else:
  211. reorderedLayers[layersOrder[i]] = layer
  212. self.SetLayers(reorderedLayers)
  213. except IOError as e:
  214. grass.warning(
  215. _("Unable to read cmdfile '%(cmd)s'. Details: %(det)s") %
  216. {'cmd': self.cmdfile, 'det': e})
  217. return
  218. Debug.msg(1, "Map.GetLayersFromCmdFile(): cmdfile=%s, nlayers=%d" %
  219. (self.cmdfile, nlayers))
  220. self._giface.updateMap.emit(render=False)
  221. def Render(self, *args, **kwargs):
  222. """Render layer to image.
  223. For input params and returned data see overridden method in Map class.
  224. """
  225. return Map.Render(self, *args, **kwargs)
  226. def AddLayer(self, *args, **kwargs):
  227. """Adds generic map layer to list of layers.
  228. For input params and returned data see overridden method in Map class.
  229. """
  230. driver = UserSettings.Get(group='display', key='driver', subkey='type')
  231. if driver == 'png':
  232. os.environ["GRASS_RENDER_IMMEDIATE"] = "png"
  233. else:
  234. os.environ["GRASS_RENDER_IMMEDIATE"] = "cairo"
  235. layer = Map.AddLayer(self, *args, **kwargs)
  236. del os.environ["GRASS_RENDER_IMMEDIATE"]
  237. return layer
  238. class Layer(object):
  239. """@implements core::giface::Layer"""
  240. def __init__(self, maplayer):
  241. self._maplayer = maplayer
  242. def __getattr__(self, name):
  243. if name == 'cmd':
  244. return cmdtuple_to_list(self._maplayer.GetCmd())
  245. elif hasattr(self._maplayer, name):
  246. return getattr(self._maplayer, name)
  247. elif name == 'maplayer':
  248. return self._maplayer
  249. elif name == 'type':
  250. return self._maplayer.GetType()
  251. # elif name == 'ctrl':
  252. elif name == 'label':
  253. return self._maplayer.GetName()
  254. # elif name == 'propwin':
  255. class LayerList(object):
  256. """@implements core::giface::LayerList"""
  257. def __init__(self, map, giface):
  258. self._map = map
  259. self._giface = giface
  260. self._index = 0
  261. def __len__(self):
  262. return len(self._map.GetListOfLayers())
  263. def __iter__(self):
  264. return self
  265. def next(self):
  266. items = self._map.GetListOfLayers()
  267. try:
  268. result = items[self._index]
  269. except IndexError:
  270. raise StopIteration
  271. self._index += 1
  272. return result
  273. def GetSelectedLayers(self, checkedOnly=True):
  274. # hidden and selected vs checked and selected
  275. items = self._map.GetListOfLayers()
  276. layers = []
  277. for item in items:
  278. layer = Layer(item)
  279. layers.append(layer)
  280. return layers
  281. def GetSelectedLayer(self, checkedOnly=False):
  282. """Returns selected layer or None when there is no selected layer."""
  283. layers = self.GetSelectedLayers()
  284. if len(layers) > 0:
  285. return layers[0]
  286. else:
  287. return None
  288. def AddLayer(self, ltype, name=None, checked=None,
  289. opacity=1.0, cmd=None):
  290. """Adds a new layer to the layer list.
  291. Launches property dialog if needed (raster, vector, etc.)
  292. :param ltype: layer type (raster, vector, raster_3d, ...)
  293. :param name: layer name
  294. :param checked: if True layer is checked
  295. :param opacity: layer opacity level
  296. :param cmd: command (given as a list)
  297. """
  298. self._map.AddLayer(ltype=ltype, command=cmd,
  299. name=name, active=True,
  300. opacity=opacity, render=True,
  301. pos=-1)
  302. # TODO: this should be solved by signal
  303. # (which should be introduced everywhere,
  304. # alternative is some observer list)
  305. self._giface.updateMap.emit(render=True, renderVector=True)
  306. def GetLayersByName(self, name):
  307. items = self._map.GetListOfLayers()
  308. layers = []
  309. for item in items:
  310. if item.GetName() == name:
  311. layer = Layer(item)
  312. layers.append(layer)
  313. return layers
  314. def GetLayerByData(self, key, value):
  315. # TODO: implementation was not tested
  316. items = self._map.GetListOfLayers()
  317. for item in items:
  318. layer = Layer(item)
  319. try:
  320. if getattr(layer, key) == value:
  321. return layer
  322. except AttributeError:
  323. pass
  324. return None
  325. class DMonGrassInterface(StandaloneGrassInterface):
  326. """@implements GrassInterface"""
  327. def __init__(self, mapframe):
  328. StandaloneGrassInterface.__init__(self)
  329. self._mapframe = mapframe
  330. def GetLayerList(self):
  331. return LayerList(self._mapframe.GetMap(), giface=self)
  332. def GetMapWindow(self):
  333. return self._mapframe.GetMapWindow()
  334. def GetMapDisplay(self):
  335. return self._mapframe
  336. def GetProgress(self):
  337. return self._mapframe.GetProgressBar()
  338. def ShowStatusbar(self, show=True):
  339. if not self._mapframe.statusbarManager:
  340. self._mapframe.CreateStatusbar()
  341. self._mapframe.statusbarManager.Show(show)
  342. def IsStatusbarShown(self):
  343. if not self._mapframe.statusbarManager:
  344. return False
  345. return self._mapframe.statusbarManager.IsShown()
  346. def ShowAllToolbars(self, show=True):
  347. if not show: # hide
  348. action = self._mapframe.RemoveToolbar
  349. else:
  350. action = self._mapframe.AddToolbar
  351. toolbars = self._mapframe.GetToolbarNames()
  352. if not toolbars:
  353. toolbars.append('map')
  354. for toolbar in toolbars:
  355. action(toolbar)
  356. def AreAllToolbarsShown(self):
  357. toolbar = self._mapframe.GetMapToolbar()
  358. if toolbar is None:
  359. return False
  360. return toolbar.IsShown()
  361. class DMonFrame(MapFrame):
  362. def OnZoomToMap(self, event):
  363. layers = self.MapWindow.GetMap().GetListOfLayers()
  364. self.MapWindow.ZoomToMap(layers=layers)
  365. def OnSize(self, event):
  366. super(DMonFrame, self).OnSize(event)
  367. # update env file
  368. width, height = self.MapWindow.GetClientSize()
  369. for line in fileinput.input(monFile['env'], inplace=True):
  370. if 'GRASS_RENDER_WIDTH' in line:
  371. print 'GRASS_RENDER_WIDTH={}'.format(width)
  372. elif 'GRASS_RENDER_HEIGHT' in line:
  373. print 'GRASS_RENDER_HEIGHT={}'.format(height)
  374. else:
  375. print line.rstrip('\n')
  376. class MapApp(wx.App):
  377. def OnInit(self):
  378. if not globalvar.CheckWxVersion([2, 9]):
  379. wx.InitAllImageHandlers()
  380. grass.set_raise_on_error(True)
  381. # actual use of StandaloneGrassInterface not yet tested
  382. # needed for adding functionality in future
  383. self._giface = DMonGrassInterface(None)
  384. return True
  385. def CreateMapFrame(self, name, decorations=True):
  386. toolbars = []
  387. if decorations:
  388. toolbars.append('map')
  389. if __name__ == "__main__":
  390. self.cmdTimeStamp = os.path.getmtime(monFile['cmd'])
  391. self.Map = DMonMap(giface=self._giface, cmdfile=monFile['cmd'],
  392. mapfile=monFile['map'])
  393. self.timer = wx.PyTimer(self.watcher)
  394. # check each 0.5s
  395. global mtime
  396. mtime = 500
  397. self.timer.Start(mtime)
  398. else:
  399. self.Map = None
  400. self.mapFrm = DMonFrame(
  401. parent=None,
  402. id=wx.ID_ANY,
  403. title=name,
  404. Map=self.Map,
  405. giface=self._giface,
  406. size=monSize,
  407. toolbars=toolbars,
  408. statusbar=decorations)
  409. # FIXME: hack to solve dependency
  410. self._giface._mapframe = self.mapFrm
  411. self.mapFrm.GetMapWindow().SetAlwaysRenderEnabled(False)
  412. self.Map.saveToFile.connect(lambda cmd: self.mapFrm.DOutFile(cmd))
  413. self.Map.dToRast.connect(lambda cmd: self.mapFrm.DToRast(cmd))
  414. self.Map.query.connect(
  415. lambda ltype,
  416. maps: self.mapFrm.SetQueryLayersAndActivate(
  417. ltype=ltype,
  418. maps=maps))
  419. return self.mapFrm
  420. def OnExit(self):
  421. if __name__ == "__main__":
  422. # stop the timer
  423. # self.timer.Stop()
  424. # terminate thread
  425. for f in monFile.itervalues():
  426. try_remove(f)
  427. def watcher(self):
  428. """Redraw, if new layer appears (check's timestamp of
  429. cmdfile)
  430. """
  431. ###
  432. # TODO: find a better solution
  433. ###
  434. # the check below disabled, it's too much invasive to call
  435. # g.gisenv in the watcher...
  436. # try:
  437. # GISBASE and other system enviromental variables can not be used
  438. # since the process inherited them from GRASS
  439. # raises exception when vaiable does not exists
  440. # grass.gisenv()['GISDBASE']
  441. # except KeyError:
  442. # self.timer.Stop()
  443. # return
  444. # todo: events
  445. try:
  446. currentCmdFileTime = os.path.getmtime(monFile['cmd'])
  447. if currentCmdFileTime > self.cmdTimeStamp:
  448. self.timer.Stop()
  449. self.cmdTimeStamp = currentCmdFileTime
  450. self.mapFrm.GetMap().GetLayersFromCmdFile()
  451. self.timer.Start(mtime)
  452. except OSError as e:
  453. grass.warning("%s" % e)
  454. self.timer.Stop()
  455. def GetMapFrame(self):
  456. """Get Map Frame instance"""
  457. return self.mapFrm
  458. if __name__ == "__main__":
  459. if len(sys.argv) != 6:
  460. print __doc__
  461. sys.exit(0)
  462. # set command variable
  463. monName = sys.argv[1]
  464. monPath = sys.argv[2]
  465. monFile = {'map': os.path.join(monPath, 'map.ppm'),
  466. 'cmd': os.path.join(monPath, 'cmd'),
  467. 'env': os.path.join(monPath, 'env')}
  468. # monitor size
  469. monSize = (int(sys.argv[3]), int(sys.argv[4]))
  470. monDecor = not bool(int(sys.argv[5]))
  471. grass.verbose(_("Starting map display <%s>...") % (monName))
  472. # create pid file
  473. pidFile = os.path.join(monPath, "pid")
  474. fd = open(pidFile, 'w')
  475. if not fd:
  476. grass.fatal(_("Unable to create file <%s>") % pidFile)
  477. fd.write("%s\n" % os.getpid())
  478. fd.close()
  479. RunCommand('g.gisenv',
  480. set='MONITOR_%s_PID=%d' % (monName.upper(), os.getpid()))
  481. start = time.time()
  482. gmMap = MapApp(0)
  483. mapFrame = gmMap.CreateMapFrame(monName, monDecor)
  484. mapFrame.Show()
  485. Debug.msg(1, "WxMonitor started in %.6f sec" %
  486. (time.time() - start))
  487. gmMap.MainLoop()
  488. grass.verbose(_("Stopping map display <%s>...") % (monName))
  489. # clean up GRASS env variables
  490. try:
  491. shutil.rmtree(monPath)
  492. except OSError:
  493. pass
  494. RunCommand('g.gisenv',
  495. unset='MONITOR')
  496. sys.exit(0)