"""! @package mapdisp.main @brief Start Map Display as standalone application Classes: - mapdisp::DMonMap - mapdisp::Layer - mapdisp::LayerList - mapdisp::DMonGrassInterface - mapdisp::DMonFrame - mapdisp::MapApp Usage: python mapdisp/main.py monitor-identifier /path/to/map/file /path/to/command/file /path/to/env/file (C) 2006-2013 by the GRASS Development Team This program is free software under the GNU General Public License (>=v2). Read the file COPYING that comes with GRASS for details. @author Michael Barton @author Jachym Cepicky @author Martin Landa @author Vaclav Petras (MapFrameBase) @author Anna Kratochvilova (MapFrameBase) """ import os import sys if __name__ == "__main__": gui_wx_path = os.path.join(os.getenv('GISBASE'), 'etc', 'gui', 'wxpython') if gui_wx_path not in sys.path: sys.path.append(gui_wx_path) from core import globalvar import wx from core import utils from core.giface import StandaloneGrassInterface from core.gcmd import RunCommand from core.render import Map, MapLayer from core.utils import _ from mapdisp.frame import MapFrame from grass.script import core as grass from core.debug import Debug from core.settings import UserSettings # for standalone app monFile = { 'cmd' : None, 'map' : None, 'env' : None, } monName = None monSize = list(globalvar.MAP_WINDOW_SIZE) class DMonMap(Map): def __init__(self, giface, cmdfile=None, mapfile=None): """!Map composition (stack of map layers and overlays) @param cmdline full path to the cmd file (defined by d.mon) @param mapfile full path to the map file (defined by d.mon) """ Map.__init__(self) self._giface = giface # environment settings self.env = dict() self.cmdfile = cmdfile # list of layers for rendering added from cmd file # TODO temporary solution, layer managment by different tools in GRASS should be resovled self.ownedLayers = [] if mapfile: self.mapfileCmd = mapfile self.maskfileCmd = os.path.splitext(mapfile)[0] + '.pgm' # generated file for g.pnmcomp output for rendering the map self.mapfile = monFile['map'] + '.ppm' def GetLayersFromCmdFile(self): """!Get list of map layers from cmdfile """ if not self.cmdfile: return nlayers = 0 try: fd = open(self.cmdfile, 'r') existingLayers = self.GetListOfLayers() # holds new rendreing order for every layer in existingLayers layersOrder = [-1] * len(self.GetListOfLayers()) # next number in rendering order next_layer = 0; for line in fd.readlines(): cmd = utils.split(line.strip()) ltype = None try: ltype = utils.command2ltype[cmd[0]] except KeyError: grass.warning(_("Unsupported command %s.") % cmd[0]) continue name = utils.GetLayerNameFromCmd(cmd, fullyQualified = True, layerType = ltype)[0] # creating temporary layer object to compare commands # neccessary to get the same format # supposing that there are no side effects tmpMapLayer = MapLayer(ltype = ltype, name = name, cmd = cmd, Map = None, active = False, hidden = True, opacity = 0) exists = False for i, layer in enumerate(existingLayers): if layer.GetCmd(string=True) == tmpMapLayer.GetCmd(string=True): exists = True if layersOrder[i] == -1: layersOrder[i] = next_layer; next_layer += 1 # layer must be put higher in render order (same cmd was insered more times) # TODO delete rendurant cmds from cmd file? else: for j, l_order in enumerate(layersOrder): if l_order > layersOrder[i]: layersOrder[j] -= 1; layersOrder[i] = next_layer - 1; break if exists: continue newLayer = Map.AddLayer(self, ltype = ltype, command = cmd, active = True, name = name) existingLayers.append(newLayer) self.ownedLayers.append(newLayer) layersOrder.append(next_layer) next_layer += 1 nlayers += 1 reorderedLayers = [-1] * next_layer for i, layer in enumerate(existingLayers): # owned layer was not found in cmd file -> is deleted if layersOrder[i] == -1 and layer in self.ownedLayers: self.ownedLayers.remove(layer) self.DeleteLayer(layer) # other layer e. g. added by wx.vnet are added to the top elif layersOrder[i] == -1 and layer not in self.ownedLayers: reorderedLayers.append(layer) # owned layer found in cmd file is added into proper rendering position else: reorderedLayers[layersOrder[i]] = layer self.ReorderLayers(reorderedLayers) except IOError, e: grass.warning(_("Unable to read cmdfile '%(cmd)s'. Details: %(det)s") % \ { 'cmd' : self.cmdfile, 'det' : e }) return fd.close() self._giface.updateMap.emit() Debug.msg(1, "Map.GetLayersFromCmdFile(): cmdfile=%s" % self.cmdfile) Debug.msg(1, " nlayers=%d" % nlayers) def Render(self, *args, **kwargs): """!Render layer to image. For input params and returned data see overridden method in Map class. """ currMon = grass.gisenv()['MONITOR'] RunCommand('g.gisenv', unset = 'MONITOR') # GRASS_RENDER_IMMEDIATE doesn't like monitors ret = Map.Render(self, *args, **kwargs) RunCommand('g.gisenv', set = 'MONITOR=%s' % currMon) return ret def AddLayer(self, *args, **kwargs): """!Adds generic map layer to list of layers. For input params and returned data see overridden method in Map class. """ currMon = grass.gisenv()['MONITOR'] RunCommand('g.gisenv', unset = 'MONITOR') # GRASS_RENDER_IMMEDIATE doesn't like monitors driver = UserSettings.Get(group = 'display', key = 'driver', subkey = 'type') if driver == 'png': os.environ["GRASS_RENDER_IMMEDIATE"] = "png" else: os.environ["GRASS_RENDER_IMMEDIATE"] = "cairo" layer = Map.AddLayer(self, *args, **kwargs) del os.environ["GRASS_RENDER_IMMEDIATE"] RunCommand('g.gisenv', set = 'MONITOR=%s' % currMon) return layer class Layer(object): def __init__(self, maplayer): self._maplayer = maplayer def __getattr__(self, name): if name == 'cmd': return utils.CmdTupleToList(self._maplayer.GetCmd()) elif hasattr(self._maplayer, name): return getattr(self._maplayer, name) elif name == 'maplayer': return self._maplayer elif name == 'type': return self._maplayer.GetType() #elif name == 'ctrl': elif name == 'label': return self._maplayer.GetName() #elif name == 'maplayer' : None, #elif name == 'propwin': class LayerList(object): def __init__(self, map): self._map = map # def __iter__(self): # for in : # yield def GetSelectedLayers(self, checkedOnly=True): # hidden and selected vs checked and selected items = self._map.GetListOfLayers() layers = [] for item in items: layer = Layer(item) layers.append(layer) return layers class DMonGrassInterface(StandaloneGrassInterface): """!@implements GrassInterface""" def __init__(self, mapframe): StandaloneGrassInterface.__init__(self) self._mapframe = mapframe def GetLayerList(self): return LayerList(self._mapframe.GetMap()) class DMonFrame(MapFrame): def OnZoomToMap(self, event): layers = self.MapWindow.GetMap().GetListOfLayers() self.MapWindow.ZoomToMap(layers = layers) class MapApp(wx.App): def OnInit(self): if not globalvar.CheckWxVersion([2, 9]): wx.InitAllImageHandlers() # actual use of StandaloneGrassInterface not yet tested # needed for adding functionality in future giface = DMonGrassInterface(None) if __name__ == "__main__": self.cmdTimeStamp = os.path.getmtime(monFile['cmd']) self.Map = DMonMap(giface=giface, cmdfile=monFile['cmd'], mapfile = monFile['map']) else: self.Map = None self.mapFrm = DMonFrame(parent = None, id = wx.ID_ANY, Map = self.Map, giface = giface, size = monSize) # FIXME: hack to solve dependency giface._mapframe = self.mapFrm # self.SetTopWindow(Map) self.mapFrm.GetMapWindow().SetAlwaysRenderEnabled(True) self.mapFrm.Show() if __name__ == "__main__": self.timer = wx.PyTimer(self.watcher) #check each 0.5s global mtime mtime = 500 self.timer.Start(mtime) return True def OnExit(self): if __name__ == "__main__": # stop the timer # self.timer.Stop() # terminate thread for f in monFile.itervalues(): grass.try_remove(f) def watcher(self): """!Redraw, if new layer appears (check's timestamp of cmdfile) """ try: # GISBASE and other sytem enviromental variables can not be used # since the process inherited them from GRASS # raises exception when vaiable does not exists grass.gisenv()['GISDBASE'] except KeyError: self.timer.Stop() return # todo: events try: currentCmdFileTime = os.path.getmtime(monFile['cmd']) if currentCmdFileTime > self.cmdTimeStamp: self.timer.Stop() self.cmdTimeStamp = currentCmdFileTime self.mapFrm.GetMap().GetLayersFromCmdFile() self.timer.Start(mtime) except OSError, e: grass.warning("%s" % e) self.timer.Stop() def GetMapFrame(self): """!Get Map Frame instance""" return self.mapFrm if __name__ == "__main__": # set command variable if len(sys.argv) < 5: print __doc__ sys.exit(1) monName = sys.argv[1] monFile = { 'map' : sys.argv[2], 'cmd' : sys.argv[3], 'env' : sys.argv[4], } if len(sys.argv) >= 6: try: monSize[0] = int(sys.argv[5]) except ValueError: pass if len(sys.argv) == 7: try: monSize[1] = int(sys.argv[6]) except ValueError: pass grass.verbose(_("Starting map display <%s>...") % (monName)) RunCommand('g.gisenv', set = 'MONITOR_%s_PID=%d' % (monName, os.getpid())) gmMap = MapApp(0) # set title gmMap.mapFrm.SetTitle(_("GRASS GIS Map Display: " + monName + " - Location: " + grass.gisenv()["LOCATION_NAME"])) gmMap.MainLoop() grass.verbose(_("Stopping map display <%s>...") % (monName)) # clean up GRASS env variables env = grass.gisenv() env_name = 'MONITOR_%s' % monName for key in env.keys(): if key.find(env_name) == 0: RunCommand('g.gisenv', unset = '%s' % key) if key == 'MONITOR' and env[key] == monName: RunCommand('g.gisenv', unset = '%s' % key) sys.exit(0)