main.py 17 KB

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