main.py 21 KB

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