main.py 19 KB

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