main.py 17 KB

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