main.py 18 KB

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