main.py 14 KB

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