class ProfileController(AnalysisControllerBase): """Class controls profiling in map display. It should be used inside ProfileFrame """ def __init__(self, giface, mapWindow): AnalysisControllerBase.__init__(self, giface=giface, mapWindow=mapWindow) self.transectChanged = Signal('ProfileController.transectChanged') self._graphicsType = 'line' def _doAnalysis(self, coords): """Informs profile dialog that profile changed. :param coords: EN coordinates """ self.transectChanged.emit(coords=coords) def _disconnectAll(self): self._mapWindow.mouseLeftDown.disconnect(self._start) self._mapWindow.mouseLeftUp.disconnect(self._addPoint) def _connectAll(self): self._mapWindow.mouseLeftDown.connect(self._start) self._mapWindow.mouseLeftUp.connect(self._addPoint) def _getPen(self): return wx.Pen(colour=wx.Colour(0, 100, 0), width=2, style=wx.SHORT_DASH) def Stop(self, restore=True): AnalysisControllerBase.Stop(self, restore=restore) self.transectChanged.emit(coords=[])
class ProfileController(AnalysisControllerBase): """Class controls profiling in map display. It should be used inside ProfileFrame """ def __init__(self, giface, mapWindow): AnalysisControllerBase.__init__(self, giface=giface, mapWindow=mapWindow) self.transectChanged = Signal("ProfileController.transectChanged") self._graphicsType = "line" def _doAnalysis(self, coords): """Informs profile dialog that profile changed. :param coords: EN coordinates """ self.transectChanged.emit(coords=coords) def _disconnectAll(self): self._mapWindow.mouseLeftDown.disconnect(self._start) self._mapWindow.mouseLeftUp.disconnect(self._addPoint) def _connectAll(self): self._mapWindow.mouseLeftDown.connect(self._start) self._mapWindow.mouseLeftUp.connect(self._addPoint) def _getPen(self): return wx.Pen(colour=wx.Colour(0, 100, 0), width=2, style=wx.SHORT_DASH) def Stop(self, restore=True): AnalysisControllerBase.Stop(self, restore=restore) self.transectChanged.emit(coords=[])
class ExportPanel(wx.Panel): def __init__(self, parent, giface, settings): wx.Panel.__init__(self, parent) self.giface = giface self.settings = settings self.settingsChanged = Signal('ScanningPanel.settingsChanged') if 'export' not in self.settings: self.settings['export'] = {} self.settings['export']['PLY'] = False self.settings['export']['PLY_file'] = '' if self.settings['export']['PLY_file']: initDir = os.path.dirname(self.settings['export']['PLY_file']) else: initDir = "" self.ifPLY = wx.CheckBox(self, label="") self.ifPLY.SetValue(self.settings['export']['PLY']) self.ifPLY.Bind(wx.EVT_CHECKBOX, self.OnChange) self.exportPLY = filebrowse.FileBrowseButton( self, labelText="Export PLY:", fileMode=wx.SAVE, startDirectory=initDir, initialValue=self.settings['export']['PLY_file'], changeCallback=self.OnChange) mainSizer = wx.BoxSizer(wx.VERTICAL) sizer = wx.BoxSizer(wx.HORIZONTAL) sizer.Add(self.ifPLY, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.LEFT, border=3) sizer.Add(self.exportPLY, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, proportion=1, border=0) mainSizer.Add(sizer, flag=wx.EXPAND | wx.ALL, border=5) self.SetSizer(mainSizer) mainSizer.Fit(self) def OnChange(self, event): self.settings['export']['PLY'] = self.ifPLY.IsChecked() self.settings['export']['PLY_file'] = self.exportPLY.GetValue() self.settingsChanged.emit()
class StatisticsData: """Stores all statistics. """ def __init__(self): self.statisticsDict = {} self.statisticsList = [] self.statisticsAdded = Signal("StatisticsData.statisticsAdded") self.statisticsDeleted = Signal("StatisticsData.statisticsDeleted") self.allStatisticsDeleted = Signal( "StatisticsData.allStatisticsDeleted") self.statisticsSet = Signal("StatisticsData.statisticsSet") def GetStatistics(self, cat): return self.statisticsDict[cat] def AddStatistics(self, cat, name, color): st = Statistics() st.SetBaseStatistics(cat=cat, name=name, color=color) st.statisticsSet.connect( lambda stats: self.statisticsSet.emit( cat=cat, stats=stats)) self.statisticsDict[cat] = st self.statisticsList.append(cat) self.statisticsAdded.emit(cat=cat, name=name, color=color) def DeleteStatistics(self, cat): del self.statisticsDict[cat] self.statisticsList.remove(cat) self.statisticsDeleted.emit(cat=cat) def GetCategories(self): return self.statisticsList[:] def DeleteAllStatistics(self): self.statisticsDict.clear() del self.statisticsList[:] # not ...=[] ! self.allStatisticsDeleted.emit()
class PlotsRenderingManager: """Manages rendering of scatter plot. .. todo:: still space for optimalization """ def __init__(self, scatt_mgr, cats_mgr, core): self.scatt_mgr = scatt_mgr self.cats_mgr = cats_mgr self.core = core self.scatts_dt, self.scatt_conds_dt = self.core.GetScattsData() self.runningProcesses = 0 self.data_to_render = {} self.render_queue = [] self.cat_ids = [] self.cat_cond_ids = [] self.renderingStarted = Signal("ScattsManager.renderingStarted") self.renderingFinished = Signal("ScattsManager.renderingFinished") def AddRenderRequest(self, scatts): for scatt_id, cat_ids in scatts: if not self.data_to_render.has_key[scatt_id]: self.data_to_render = cat_ids else: for c in cat_ids: if c not in self.data_to_render[scatt_id]: self.data_to_render[scatt_id].append(c) def NewRunningProcess(self): self.runningProcesses += 1 def RunningProcessDone(self): self.runningProcesses -= 1 if self.runningProcesses <= 1: self.RenderScattPlts() def RenderRequest(self): if self.runningProcesses <= 1: self.RenderScattPlts() def CategoryChanged(self, cat_ids): for c in cat_ids: if c not in self.cat_ids: self.cat_ids.append(c) def CategoryCondsChanged(self, cat_ids): for c in cat_ids: if c not in self.cat_cond_ids: self.cat_cond_ids.append(c) def RenderScattPlts(self, scatt_ids=None): if len(self.render_queue) > 1: return self.renderingStarted.emit() self.render_queue.append(self.scatt_mgr.thread.GetId()) cats_attrs = deepcopy(self.cats_mgr.GetCategoriesAttrs()) cats = self.cats_mgr.GetCategories()[:] self.scatt_mgr.thread.Run( callable=self._renderscattplts, scatt_ids=scatt_ids, cats=cats, cats_attrs=cats_attrs, ondone=self.RenderingDone) def _renderscattplts(self, scatt_ids, cats, cats_attrs): cats.reverse() cats.insert(0, 0) for i_scatt_id, scatt in self.scatt_mgr.plots.items(): if scatt_ids is not None and \ i_scatt_id not in scatt_ids: continue if not scatt['scatt']: continue scatt_dt = self.scatts_dt.GetScatt(i_scatt_id) if self._showConfEllipses(): ellipses_dt = self.scatts_dt.GetEllipses( i_scatt_id, cats_attrs) else: ellipses_dt = {} for c in six.iterkeys(scatt_dt): try: self.cat_ids.remove(c) scatt_dt[c]['render'] = True except: scatt_dt[c]['render'] = False if self.scatt_mgr.pol_sel_mode[0]: self._getSelectedAreas(cats, i_scatt_id, scatt_dt, cats_attrs) scatt['scatt'].Plot(cats_order=cats, scatts=scatt_dt, ellipses=ellipses_dt, styles=cats_attrs) def RenderingDone(self, event): self.render_queue.remove(event.pid) if not self.render_queue: self.renderingFinished.emit() def _getSelectedAreas(self, cats_order, scatt_id, scatt_dt, cats_attrs): cat_id = self.cats_mgr.GetSelectedCat() if not cat_id: return sel_a_cat_id = -1 s = self.scatt_conds_dt.GetScatt(scatt_id, [cat_id]) if not s: return cats_order.append(sel_a_cat_id) col = UserSettings.Get(group='scatt', key='selection', subkey='sel_area') col = ":".join(map(str, col)) opac = UserSettings.Get(group='scatt', key='selection', subkey='sel_area_opacty') / 100.0 cats_attrs[sel_a_cat_id] = {'color': col, 'opacity': opac, 'show': True} scatt_dt[sel_a_cat_id] = s[cat_id] scatt_dt[sel_a_cat_id]['render'] = False if cat_id in self.cat_cond_ids: scatt_dt[sel_a_cat_id]['render'] = True self.cat_cond_ids.remove(cat_id) def _showConfEllipses(self): return UserSettings.Get(group='scatt', key="ellipses", subkey="show_ellips")
class GPrompt(object): """Abstract class for interactive wxGUI prompt Signal promptRunCmd - emitted to run command from prompt - attribute 'cmd' See subclass GPromptPopUp and GPromptSTC. """ def __init__(self, parent, menuModel): self.parent = parent # GConsole self.panel = self.parent.GetPanel() self.promptRunCmd = Signal('GPrompt.promptRunCmd') # probably only subclasses need this self._menuModel = menuModel self.mapList = self._getListOfMaps() self.mapsetList = utils.ListOfMapsets() # auto complete items self.autoCompList = list() self.autoCompFilter = None # command description (gtask.grassTask) self.cmdDesc = None self.cmdbuffer = self._readHistory() self.cmdindex = len(self.cmdbuffer) # list of traced commands self.commands = list() def _readHistory(self): """Get list of commands from history file""" hist = list() env = grass.gisenv() try: fileHistory = codecs.open(os.path.join(env['GISDBASE'], env['LOCATION_NAME'], env['MAPSET'], '.bash_history'), encoding = 'utf-8', mode = 'r', errors='replace') except IOError: return hist try: for line in fileHistory.readlines(): hist.append(line.replace('\n', '')) finally: fileHistory.close() return hist def _getListOfMaps(self): """Get list of maps""" result = dict() result['raster'] = grass.list_strings('raster') result['vector'] = grass.list_strings('vector') return result def _runCmd(self, cmdString): """Run command :param str cmdString: command to run """ if not cmdString: return self.commands.append(cmdString) # trace commands # parse command into list try: cmd = utils.split(str(cmdString)) except UnicodeError: cmd = utils.split(EncodeString((cmdString))) cmd = map(DecodeString, cmd) self.promptRunCmd.emit(cmd=cmd) # add command to history & clean prompt ### self.UpdateCmdHistory(cmd) self.OnCmdErase(None) self.ShowStatusText('') def GetCommands(self): """Get list of launched commands""" return self.commands def ClearCommands(self): """Clear list of commands""" del self.commands[:]
class ManageSettingsWidget(wx.Panel): """Widget which allows loading and saving settings into file.""" def __init__(self, parent, settingsFile): """ Signals: settingsChanged - called when users changes setting - attribute 'data' with chosen setting data settingsSaving - called when settings are saving - attribute 'name' with chosen settings name settingsLoaded - called when settings are loaded - attribute 'settings' is dict with loaded settings {nameofsetting : settingdata, ....} :param settingsFile: path to file, where settings will be saved and loaded from """ self.settingsFile = settingsFile self.settingsChanged = Signal('ManageSettingsWidget.settingsChanged') self.settingsSaving = Signal('ManageSettingsWidget.settingsSaving') self.settingsLoaded = Signal('ManageSettingsWidget.settingsLoaded') wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY) self.settingsBox = wx.StaticBox(parent=self, id=wx.ID_ANY, label=" %s " % _("Settings")) self.settingsChoice = wx.Choice(parent=self, id=wx.ID_ANY) self.settingsChoice.Bind(wx.EVT_CHOICE, self.OnSettingsChanged) self.btnSettingsSave = wx.Button(parent=self, id=wx.ID_SAVE) self.btnSettingsSave.Bind(wx.EVT_BUTTON, self.OnSettingsSave) self.btnSettingsSave.SetToolTipString(_("Save current settings")) self.btnSettingsDel = wx.Button(parent=self, id=wx.ID_REMOVE) self.btnSettingsDel.Bind(wx.EVT_BUTTON, self.OnSettingsDelete) self.btnSettingsSave.SetToolTipString(_("Delete currently selected settings")) # escaping with '$' character - index in self.esc_chars self.e_char_i = 0 self.esc_chars = ['$', ';'] self._settings = self._loadSettings() # -> self.settingsChoice.SetItems() self.settingsLoaded.emit(settings=self._settings) self.data_to_save = [] self._layout() self.SetSizer(self.settingsSizer) self.settingsSizer.Fit(self) def _layout(self): self.settingsSizer = wx.StaticBoxSizer(self.settingsBox, wx.HORIZONTAL) self.settingsSizer.Add(item=wx.StaticText(parent=self, id=wx.ID_ANY, label=_("Load settings:")), flag=wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, border=5) self.settingsSizer.Add(item=self.settingsChoice, proportion=1, flag=wx.EXPAND) self.settingsSizer.Add(item=self.btnSettingsSave, flag=wx.LEFT | wx.RIGHT, border=5) self.settingsSizer.Add(item=self.btnSettingsDel, flag=wx.RIGHT,border=5) def OnSettingsChanged(self, event): """Load named settings""" name = event.GetString() if name not in self._settings: GError(parent = self, message = _("Settings <%s> not found") % name) return data = self._settings[name] self.settingsChanged.emit(data=data) def GetSettings(self): """Load named settings""" return self._settings.copy() def OnSettingsSave(self, event): """Save settings""" dlg = wx.TextEntryDialog(parent=self, message=_("Name:"), caption=_("Save settings")) if dlg.ShowModal() == wx.ID_OK: name = dlg.GetValue() if not name: GMessage(parent=self, message=_("Name not given, settings is not saved.")) else: self.settingsSaving.emit(name=name) dlg.Destroy() def SaveSettings(self, name): # check if settings item already exists if name in self._settings: dlgOwt = wx.MessageDialog(self, message=_("Settings <%s> already exists. " "Do you want to overwrite the settings?") % name, caption=_("Save settings"), style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION) if dlgOwt.ShowModal() != wx.ID_YES: dlgOwt.Destroy() return if self.data_to_save: self._settings[name] = self.data_to_save self._saveSettings() self.settingsChoice.SetStringSelection(name) self.data_to_save = [] def _saveSettings(self): """Save settings and reload if successful""" if self._writeSettings() == 0: self._settings = self._loadSettings() def SetDataToSave(self, data): """Set data for setting, which will be saved. :param data: - list of strings, which will be saved """ self.data_to_save = data def SetSettings(self, settings): """Set settings :param settings: - dict with all settigs {nameofsetting : settingdata, ....} """ self._settings = settings self._saveSettings() def AddSettings(self, settings): """Add settings :param settings: - dict with all settigs {nameofsetting : settingdata, ....} """ self._settings = dict(self._settings.items() + settings.items()) self._saveSettings() def OnSettingsDelete(self, event): """Save settings """ name = self.settingsChoice.GetStringSelection() if not name: GMessage(parent = self, message = _("No settings is defined. Operation canceled.")) return self._settings.pop(name) if self._writeSettings() == 0: self._settings = self._loadSettings() def _writeSettings(self): """Save settings into the file :return: 0 on success :return: -1 on failure """ try: fd = open(self.settingsFile, 'w') fd.write('format_version=2.0\n') for key, values in self._settings.iteritems(): first = True for v in values: # escaping characters for e_ch in self.esc_chars: v = v.replace(e_ch, self.esc_chars[self.e_char_i] + e_ch) if first: # escaping characters for e_ch in self.esc_chars: key = key.replace(e_ch, self.esc_chars[self.e_char_i] + e_ch) fd.write('%s;%s;' % (key, v)) first = False else: fd.write('%s;' % (v)) fd.write('\n') except IOError: GError(parent = self, message = _("Unable to save settings")) return -1 fd.close() return 0 def _loadSettings(self): """Load settings from the file The file is defined by self.SettingsFile. :return: parsed dict :return: empty dict on error """ data = dict() if not os.path.exists(self.settingsFile): return data try: fd = open(self.settingsFile, 'r') except IOError: return data fd_lines = fd.readlines() if not fd_lines: fd.close() return data if fd_lines[0].strip() == 'format_version=2.0': data = self._loadSettings_v2(fd_lines) else: data = self._loadSettings_v1(fd_lines) self.settingsChoice.SetItems(sorted(data.keys())) fd.close() self.settingsLoaded.emit(settings=data) return data def _loadSettings_v2(self, fd_lines): """Load settings from the file in format version 2.0 The file is defined by self.SettingsFile. :return: parsed dict :return: empty dict on error """ data = dict() for line in fd_lines[1:]: try: lineData = [] line = line.rstrip('\n') i_last_found = i_last = 0 key = '' while True: idx = line.find(';', i_last) if idx < 0: break elif idx != 0: # find out whether it is separator # $$$$; - it is separator # $$$$$; - it is not separator i_esc_chars = 0 while True: if line[idx - (i_esc_chars + 1)] == self.esc_chars[self.e_char_i]: i_esc_chars += 1 else: break if i_esc_chars%2 != 0: i_last = idx + 1 continue lineItem = line[i_last_found : idx] # unescape characters for e_ch in self.esc_chars: lineItem = lineItem.replace(self.esc_chars[self.e_char_i] + e_ch, e_ch) if i_last_found == 0: key = lineItem else: lineData.append(lineItem) i_last_found = i_last = idx + 1 if key and lineData: data[key] = lineData except ValueError: pass return data def _loadSettings_v1(self, fd_lines): """Load settings from the file in format version 1.0 (backward compatibility) The file is defined by self.SettingsFile. :return: parsed dict :return: empty dict on error """ data = dict() for line in fd_lines: try: lineData = line.rstrip('\n').split(';') if len(lineData) > 4: # type, dsn, format, options data[lineData[0]] = (lineData[1], lineData[2], lineData[3], lineData[4]) else: data[lineData[0]] = (lineData[1], lineData[2], lineData[3], '') except ValueError: pass return data
class SwipeMapDialog(wx.Dialog): """Dialog used to select maps. There are two modes - simple (only two raster maps), or two layer lists. """ def __init__(self, parent, title=_("Select raster maps"), first=None, second=None, firstLayerList=None, secondLayerList=None): wx.Dialog.__init__(self, parent=parent, title=title, style=wx.RESIZE_BORDER | wx.DEFAULT_DIALOG_STYLE) if firstLayerList is None: self._firstLayerList = LayerList() else: self._firstLayerList = copy.deepcopy(firstLayerList) if secondLayerList is None: self._secondLayerList = LayerList() else: self._secondLayerList = copy.deepcopy(secondLayerList) self._firstPanel = self._createSimplePanel() self._secondPanel = self._createAdvancedPanel() self.btnSwitch = wx.Button(self) self.btnCancel = wx.Button(self, id=wx.ID_CANCEL) self.btnApply = wx.Button(self, id=wx.ID_APPLY) self.btnOK = wx.Button(self, id=wx.ID_OK) self.btnOK.SetDefault() self.btnSwitch.Bind(wx.EVT_BUTTON, self.OnSwitchMode) self.btnApply.Bind(wx.EVT_BUTTON, lambda evt: self._apply()) self.btnOK.Bind(wx.EVT_BUTTON, lambda evt: self._ok()) self.btnCancel.Bind(wx.EVT_BUTTON, lambda evt: self.Close()) self.Bind(wx.EVT_CLOSE, lambda evt: self.Hide()) self.applyChanges = Signal('SwipeMapDialog.applyChanges') if first: self._firstRaster.SetValue(first) if second: self._secondRaster.SetValue(second) self._layout() def _layout(self): """Do layout""" mainSizer = wx.BoxSizer(wx.VERTICAL) self._switchSizer = wx.BoxSizer() self._switchSizer.Add(self._firstPanel, proportion=1, flag=wx.EXPAND | wx.ALL, border=5) self._switchSizer.Add(self._secondPanel, proportion=1, flag=wx.EXPAND | wx.ALL, border=5) mainSizer.Add(self._switchSizer, proportion=1, flag=wx.EXPAND | wx.ALL) self.btnSizer = wx.StdDialogButtonSizer() self.btnSizer.AddButton(self.btnCancel) self.btnSizer.AddButton(self.btnOK) self.btnSizer.AddButton(self.btnApply) self.btnSizer.Realize() mainSizer.Add(item=self.btnSwitch, proportion=0, flag=wx.ALL | wx.ALIGN_LEFT, border=5) mainSizer.Add(item=self.btnSizer, proportion=0, flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5) self.mainSizer = mainSizer self.SetSizer(mainSizer) mainSizer.Fit(self) self._switchMode(simple=True) def _createSimplePanel(self): panel = wx.Panel(self) sizer = wx.BoxSizer(wx.VERTICAL) self._firstRaster = gselect.Select(parent=panel, type='raster', size=globalvar.DIALOG_GSELECT_SIZE, validator=SimpleValidator(callback=self.ValidatorCallback)) self._secondRaster = gselect.Select(parent=panel, type='raster', size=globalvar.DIALOG_GSELECT_SIZE, validator=SimpleValidator(callback=self.ValidatorCallback)) sizer.Add(wx.StaticText(panel, label=_("Name of top/left raster map:")), proportion=0, flag=wx.EXPAND | wx.ALL, border=5) sizer.Add(self._firstRaster, proportion=0, flag=wx.EXPAND | wx.ALL, border=1) sizer.Add(wx.StaticText(panel, label=_("Name of bottom/right raster map:")), proportion=0, flag=wx.EXPAND | wx.ALL, border=1) sizer.Add(self._secondRaster, proportion=0, flag=wx.EXPAND | wx.ALL, border=1) self._firstRaster.SetFocus() panel.SetSizer(sizer) sizer.Fit(panel) return panel def _createAdvancedPanel(self): panel = wx.Panel(self) sizer = wx.BoxSizer(wx.HORIZONTAL) self._firstLmgr = SimpleLayerManager(parent=panel, layerList=self._firstLayerList, lmgrStyle=SIMPLE_LMGR_RASTER | SIMPLE_LMGR_RGB | SIMPLE_LMGR_VECTOR | SIMPLE_LMGR_TB_LEFT) self._secondLmgr = SimpleLayerManager(parent=panel, layerList=self._secondLayerList, lmgrStyle=SIMPLE_LMGR_RASTER | SIMPLE_LMGR_RGB | SIMPLE_LMGR_VECTOR | SIMPLE_LMGR_TB_RIGHT) sizer.Add(self._firstLmgr, proportion=1, flag=wx.EXPAND | wx.ALL, border=5) sizer.Add(self._secondLmgr, proportion=1, flag=wx.EXPAND | wx.ALL, border=5) panel.SetSizer(sizer) sizer.Fit(panel) return panel def _switchMode(self, simple): if simple: self._switchSizer.Show(self._firstPanel, show=True, recursive=True) self._switchSizer.Show(self._secondPanel, show=False, recursive=True) self.btnSwitch.SetLabel(_("Switch to advanced mode")) self.btnCancel.SetLabel(_("Cancel")) else: self._switchSizer.Show(self._firstPanel, show=False, recursive=True) self._switchSizer.Show(self._secondPanel, show=True, recursive=True) self.btnSwitch.SetLabel(_("Switch to simple mode")) self.btnCancel.SetLabel(_("Close")) self.Freeze() # doesn't do anything (at least on Ubuntu) self.btnSizer.Show(self.btnApply, simple) self.btnSizer.Show(self.btnOK, simple) self.btnSizer.Layout() self._switchSizer.Layout() self.Fit() self.Thaw() self.applyChanges.emit() def OnSwitchMode(self, event): if self._switchSizer.IsShown(self._secondPanel): self._switchMode(simple=True) else: self._switchMode(simple=False) def ValidatorCallback(self, win): if self._switchSizer.IsShown(self._secondPanel): return if win == self._firstRaster.GetTextCtrl(): GMessage(parent=self, message=_("Name of the first map is missing.")) else: GMessage(parent=self, message=_("Name of the second map is missing.")) def _ok(self): self._apply() self.Close() def _apply(self): # TODO check if not empty self.applyChanges.emit() def GetValues(self): """Get raster maps""" if self.IsSimpleMode(): return (self._firstRaster.GetValue(), self._secondRaster.GetValue()) else: return (self._firstLayerList, self._secondLayerList) def IsSimpleMode(self): if self._switchSizer.IsShown(self._firstPanel): return True return False def GetFirstSimpleLmgr(self): return self._firstLmgr def GetSecondSimpleLmgr(self): return self._secondLmgr
class VNETPointsData: def __init__(self, mapWin, an_data, an_params): self.mapWin = mapWin self.an_data = an_data self.an_params = an_params # information, whether mouse event handler is registered in map window self.handlerRegistered = False self.pointsChanged = Signal("VNETPointsData.pointsChanged") self.an_params.parametersChanged.connect(self.ParametersChanged) self.snapping = False self.data = [] self.cols = { "name": ["use", "type", "topology", "e", "n"], "label": [_("use"), _("type"), _("topology"), "e", "n"], # TDO "type": [None, ["", _("Start point"), _("End Point")], None, float, float], "def_vals": [False, 0, "new point", 0, 0], } # registration graphics for drawing self.pointsToDraw = self.mapWin.RegisterGraphicsToDraw( graphicsType="point", setStatusFunc=self.SetPointStatus ) self.SetPointDrawSettings() self.AddPoint() self.AddPoint() self.SetPointData(0, {"use": True, "type": 1}) self.SetPointData(1, {"use": True, "type": 2}) self.selected = 0 def __del__(self): self.CleanUp() def CleanUp(self): self.mapWin.UnregisterGraphicsToDraw(self.pointsToDraw) if self.handlerRegistered: self.mapWin.UnregisterMouseEventHandler( wx.EVT_LEFT_DOWN, self.OnMapClickHandler ) def SetSnapping(self, activate): self.snapping = activate def GetSnapping(self): return self.snapping def AddPoint(self): self.pointsToDraw.AddItem( coords=(self.cols["def_vals"][3], self.cols["def_vals"][4]) ) self.data.append(self.cols["def_vals"][:]) self.pointsChanged.emit(method="AddPoint", kwargs={}) def DeletePoint(self, pt_id): item = self.pointsToDraw.GetItem(pt_id) if item: self.pointsToDraw.DeleteItem(item) self.data.pop(pt_id) self.pointsChanged.emit(method="DeletePoint", kwargs={"pt_id": pt_id}) def SetPoints(self, pts_data): for item in self.pointsToDraw.GetAllItems(): self.pointsToDraw.DeleteItem(item) self.data = [] for pt_data in pts_data: pt_data_list = self._ptDataToList(pt_data) self.data.append(pt_data_list) self.pointsToDraw.AddItem(coords=(pt_data_list[3], pt_data_list[4])) self.pointsChanged.emit(method="SetPoints", kwargs={"pts_data": pts_data}) def SetPointData(self, pt_id, data): for col, v in six.iteritems(data): if col == "use": continue idx = self.cols["name"].index(col) self.data[pt_id][idx] = v # if type is changed checked columns must be recalculated by _usePoint if "type" in data and "use" not in data: data["use"] = self.GetPointData(pt_id)["use"] if "use" in data: if self._usePoint(pt_id, data["use"]) == -1: data["use"] = False idx = self.cols["name"].index("use") self.data[pt_id][idx] = data["use"] self.pointsChanged.emit( method="SetPointData", kwargs={"pt_id": pt_id, "data": data} ) def GetPointData(self, pt_id): return self._ptListDataToPtData(self.data[pt_id]) def GetPointsCount(self): return len(self.data) def SetPointStatus(self, item, itemIndex): """Before point is drawn, decides properties of drawing style""" analysis, valid = self.an_params.GetParam("analysis") cats = self.an_data[analysis]["cmdParams"]["cats"] if itemIndex == self.selected: wxPen = "selected" elif not self.data[itemIndex][0]: wxPen = "unused" item.hide = False elif len(cats) > 1: idx = self.data[itemIndex][1] if idx == 2: # End/To/Sink point wxPen = "used2cat" else: wxPen = "used1cat" else: wxPen = "used1cat" item.SetPropertyVal("label", str(itemIndex + 1)) item.SetPropertyVal("penName", wxPen) def SetSelected(self, pt_id): self.selected = pt_id self.pointsChanged.emit(method="SetSelected", kwargs={"pt_id": pt_id}) def GetSelected(self): return self.selected def SetPointDrawSettings(self): """Set settings for drawing of points""" ptSize = int( UserSettings.Get(group="vnet", key="point_symbol", subkey="point_size") ) self.pointsToDraw.SetPropertyVal("size", ptSize) colors = UserSettings.Get(group="vnet", key="point_colors") ptWidth = int( UserSettings.Get(group="vnet", key="point_symbol", subkey="point_width") ) textProp = self.pointsToDraw.GetPropertyVal("text") textProp["font"].SetPointSize(ptSize + 2) for colKey, col in six.iteritems(colors): pen = self.pointsToDraw.GetPen(colKey) if pen: pen.SetColour(wx.Colour(col[0], col[1], col[2], 255)) pen.SetWidth(ptWidth) else: self.pointsToDraw.AddPen( colKey, wx.Pen( colour=wx.Colour(col[0], col[1], col[2], 255), width=ptWidth ), ) def ParametersChanged(self, method, kwargs): if "analysis" in list(kwargs["changed_params"].keys()): self._updateTypeCol() if self.an_params.GetParam("analysis")[0] == "v.net.path": self._vnetPathUpdateUsePoints(None) def _updateTypeCol(self): """Rename category values when module is changed. Expample: Start point -> Sink point""" colValues = [""] analysis, valid = self.an_params.GetParam("analysis") anParamsCats = self.an_data[analysis]["cmdParams"]["cats"] for ptCat in anParamsCats: colValues.append(ptCat[1]) type_idx = self.cols["name"].index("type") self.cols["type"][type_idx] = colValues def _ptDataToList(self, pt_data): pt_list_data = [None] * len(self.cols["name"]) for k, val in six.iteritems(pt_data): pt_list_data[self.cols["name"].index(k)] = val return pt_list_data def _ptListDataToPtData(self, pt_list_data): pt_data = {} for i, val in enumerate(pt_list_data): pt_data[self.cols["name"][i]] = val return pt_data def _usePoint(self, pt_id, use): """Item is checked/unchecked""" analysis, valid = self.an_params.GetParam("analysis") cats = self.an_data[analysis]["cmdParams"]["cats"] # TODO move # if self.updateMap: # up_map_evt = gUpdateMap(render = False, renderVector = False) # wx.PostEvent(self.dialog.mapWin, up_map_evt) if len(cats) <= 1: return 0 use_idx = self.cols["name"].index("use") checkedVal = self.data[pt_id][1] # point without given type cannot be selected if checkedVal == 0: self.data[pt_id][use_idx] = False self.pointsChanged.emit( method="SetPointData", kwargs={"pt_id": pt_id, "data": {"use": False}} ) return -1 if analysis == "v.net.path" and use: self._vnetPathUpdateUsePoints(pt_id) def _vnetPathUpdateUsePoints(self, checked_pt_id): alreadyChecked = [] type_idx = self.cols["name"].index("type") use_idx = self.cols["name"].index("use") if checked_pt_id is not None: checkedKey = checked_pt_id alreadyChecked.append(self.data[checked_pt_id][type_idx]) else: checkedKey = -1 for iKey, dt in enumerate(self.data): pt_type = dt[type_idx] if ( (pt_type in alreadyChecked and checkedKey != iKey) or pt_type == 0 ) and self.data[iKey][use_idx]: self.data[iKey][use_idx] = False self.pointsChanged.emit( method="SetPointData", kwargs={"pt_id": iKey, "data": {"use": False}}, ) elif self.data[iKey][use_idx]: alreadyChecked.append(pt_type) def EditPointMode(self, activate): """Registers/unregisters mouse handler into map window""" if activate == self.handlerRegistered: return if activate: self.mapWin.RegisterMouseEventHandler( wx.EVT_LEFT_DOWN, self.OnMapClickHandler, "cross" ) self.handlerRegistered = True else: self.mapWin.UnregisterMouseEventHandler( wx.EVT_LEFT_DOWN, self.OnMapClickHandler ) self.handlerRegistered = False self.pointsChanged.emit(method="EditMode", kwargs={"activated": activate}) def IsEditPointModeActive(self): return self.handlerRegistered def OnMapClickHandler(self, event): """Take coordinates from map window""" # TODO update snapping after input change if event == "unregistered": self.handlerRegistered = False return if not self.data: self.AddPoint() e, n = self.mapWin.GetLastEN() if self.snapping: # compute threshold snapTreshPix = int( UserSettings.Get(group="vnet", key="other", subkey="snap_tresh") ) res = max(self.mapWin.Map.region["nsres"], self.mapWin.Map.region["ewres"]) snapTreshDist = snapTreshPix * res params, err_params, flags = self.an_params.GetParams() vectMap = params["input"] if "input" in err_params: msg = _("new point") coords = SnapToNode(e, n, snapTreshDist, vectMap) if coords: e = coords[0] n = coords[1] msg = "snapped to node" else: msg = _("new point") else: msg = _("new point") self.SetPointData(self.selected, {"topology": msg, "e": e, "n": n}) self.pointsToDraw.GetItem(self.selected).SetCoords([e, n]) if self.selected == len(self.data) - 1: self.SetSelected(0) else: self.SetSelected(self.GetSelected() + 1) def GetColumns(self, only_relevant=True): cols_data = deepcopy(self.cols) hidden_cols = [] hidden_cols.append(self.cols["name"].index("e")) hidden_cols.append(self.cols["name"].index("n")) analysis, valid = self.an_params.GetParam("analysis") if only_relevant and len(self.an_data[analysis]["cmdParams"]["cats"]) <= 1: hidden_cols.append(self.cols["name"].index("type")) i_red = 0 hidden_cols.sort() for idx in hidden_cols: for dt in six.itervalues(cols_data): dt.pop(idx - i_red) i_red += 1 return cols_data
class VNETManager: def __init__(self, guiparent, giface): self.data = {} self.guiparent = guiparent self.giface = giface self.mapWin = giface.GetMapWindow() self.goutput = GConsole(guiparent=guiparent) self.vnet_data = VNETData(guiparent=guiparent, mapWin=self.mapWin) self.results = { "analysis": None, "vect_map": None } # TODO more results # this class instance manages all temporary vector maps created during # life of VNETDialog self.tmp_maps = VNETTmpVectMaps(parent=guiparent, mapWin=self.mapWin) # initialization of History class used for saving and reading data from file # it is used for browsing analysis results self.history = VNETHistory(self.guiparent, self.vnet_data, self.tmp_maps) self.analyses = VNETAnalyses(self.vnet_data, self.RunAnDone, self.goutput, self.tmp_maps) self.snap_nodes = SnappingNodes(self.giface, self.vnet_data, self.tmp_maps, self.mapWin) self.ttbCreated = Signal('VNETManager.ttbCreated') self.analysisDone = Signal('VNETManager.analysisDone') self.pointsChanged = self.vnet_data.pointsChanged self.parametersChanged = self.vnet_data.parametersChanged self.snapping = self.snap_nodes.snapping self.pointsChanged.connect(self.PointsChanged) def __del__(self): self.CleanUp() def CleanUp(self): """Removes temp layers, unregisters handlers and graphics""" update = self.tmp_maps.DeleteAllTmpMaps() self.vnet_data.CleanUp() if update: self.giface.updateMap.emit(render=True, renderVector=True) else: self.giface.updateMap.emit(render=False, renderVector=False) def GetPointsManager(self): return self.vnet_data.GetPointsData() def GetGlobalTurnsData(self): return self.vnet_data.GetGlobalTurnsData() def RunAnalysis(self): analysis, valid = self.vnet_data.GetParam("analysis") params, err_params, flags = self.vnet_data.GetParams() relevant_params = self.vnet_data.GetRelevantParams(analysis) if not relevant_params: return -1 if not self.vnet_data.InputsErrorMsgs(_("Unable to perform analysis."), analysis, params, flags, err_params, relevant_params): return -2 if self.results["vect_map"]: self.results["vect_map"].DeleteRenderLayer() # history - delete data in buffer for hist step self.history.DeleteNewHistStepData() # create new map (included to history) for result of analysis self.results["vect_map"] = self.history.NewTmpVectMapToHist( 'vnet_tmp_result') if not self.results["vect_map"]: return False # for case there is some map with same name # (when analysis does not produce any map, this map would have been shown as result) RunCommand('g.remove', flags='f', type='vector', name=self.results["vect_map"].GetVectMapName()) # save data from self.history._saveAnInputToHist(analysis, params, flags) ret = self.analyses.RunAnalysis( self.results["vect_map"].GetVectMapName(), params, flags) if not ret: return -3 else: return 1 def RunAnDone(self, cmd, returncode, results): self.results["analysis"] = cmd[0] self.results["vect_map"].SaveVectMapState() cmd, cmd_colors = self.vnet_data.GetLayerStyle() self.results["vect_map"].AddRenderLayer(cmd, cmd_colors) self.history.SaveHistStep() self.analysisDone.emit() def ShowResult(self, show): # TODO can be more results e. g. smallest cut if show: self._checkResultMapChanged(self.results["vect_map"]) cmd, cmd_colors = self.vnet_data.GetLayerStyle() self.results["vect_map"].AddRenderLayer(cmd, cmd_colors) else: self.results["vect_map"].DeleteRenderLayer() self.giface.updateMap.emit(render=True, renderVector=True) def GetAnalysisProperties(self, analysis=None): return self.vnet_data.GetAnalysisProperties(analysis=analysis) def GetResults(self): return self.results["vect_map"] def Undo(self): self._updateDataForHistStep(self.history.Undo()) # SetUpdateMap TODO return self.history.GetHistStep() def Redo(self): self._updateDataForHistStep(self.history.Redo()) # SetUpdateMap return self.history.GetHistStep() def _updateDataForHistStep(self, data): if not data: return analysis, resultMapName, params, flags = data self.results["analysis"] = analysis self.vnet_data.SetParams(params, flags) self.results["vect_map"].DeleteRenderLayer() self.results["vect_map"] = self.tmp_maps.GetTmpVectMap(resultMapName) self._checkResultMapChanged(self.results["vect_map"]) cmd, cmd_colors = self.vnet_data.GetLayerStyle() self.results["vect_map"].AddRenderLayer(cmd, cmd_colors) self.giface.updateMap.emit(render=True, renderVector=True) def GetHistStep(self): return self.history.GetHistStep() def SetParams(self, params, flags): self.vnet_data.SetParams(params, flags) def GetParams(self): params, inv_params, flags = self.vnet_data.GetParams() return params, inv_params, flags def GetParam(self, param): return self.vnet_data.GetParam(param) def _checkResultMapChanged(self, resultVectMap): """Check if map was modified outside""" if resultVectMap.VectMapState() == 0: dlg = wx.MessageDialog( parent=self, message=_("Temporary map '%s' with result " + "was changed outside vector network analysis tool.\n" + "Showed result may not correspond " + "original analysis result.") % resultVectMap.GetVectMapName(), caption=_("Result changed outside"), style=wx.ICON_INFORMATION | wx.CENTRE) dlg.ShowModal() dlg.Destroy() def IsSnappingActive(self): return self.vnet_data.GetSnapping() def Snapping(self, activate): self.snap_nodes.ComputeNodes(activate) def GetAnalyses(self): return self.vnet_data.GetAnalyses() def SettingsUpdated(self): self.vnet_data.GetPointsData().SetPointDrawSettings() if not self.results["vect_map"] or not self.tmp_maps.HasTmpVectMap( self.results["vect_map"].GetVectMapName()): self.giface.updateMap.emit(render=False, renderVector=False) elif self.results["vect_map"].GetRenderLayer(): cmd, cmd_colors = self.vnet_data.GetLayerStyle() self.results["vect_map"].AddRenderLayer(cmd, cmd_colors) self.giface.updateMap.emit(render=True, renderVector=True) # TODO optimization else: self.giface.updateMap.emit(render=False, renderVector=False) def PointsChanged(self, method, kwargs): self.giface.updateMap.emit(render=False, renderVector=False) def CreateTttb(self, params): outputMap = params["output"] mapName, mapSet = ParseMapStr(outputMap) if mapSet != grass.gisenv()['MAPSET']: GMessage(parent=self, message=_("Map can be created only in current mapset")) return False existsMap = grass.find_file(name=mapName, element='vector', mapset=grass.gisenv()['MAPSET']) if existsMap["name"]: dlg = wx.MessageDialog(parent=self.guiparent, message=_("Vector map %s already exists. " + "Do you want to overwrite it?") % (existsMap["fullname"]), caption=_("Overwrite vector map"), style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION | wx.CENTRE) ret = dlg.ShowModal() dlg.Destroy() if ret == wx.ID_NO: return False cmdTtb = [ "v.net.turntable", "input=" + params["input"], "output=" + params["output"], "arc_layer=" + params["arc_layer"], "turn_layer=" + params["turn_layer"], "turn_cat_layer=" + params["turn_cat_layer"], "--overwrite", ] self.goutput.RunCmd(command=cmdTtb, onDone=self._createTtbDone) return True def _createTtbDone(self, event): if event.returncode != 0: GMessage(parent=self.guiparent, message=_("Creation of turntable failed.")) return else: params = {} for c in event.cmd: spl_c = c.split("=") if len(spl_c) != 2: continue if spl_c[0] and spl_c != "input": params[spl_c[0]] = spl_c[1] if spl_c[0] == "output": params["input"] = spl_c[1] self.vnet_data.SetParams(params, {}) self.ttbCreated.emit(returncode=event.returncode) def SaveTmpLayer(self, layer_name): """Permanently saves temporary map of analysis result""" msg = _("Vector map with analysis result does not exist.") if not hasattr(self.results["vect_map"], "GetVectMapName"): GMessage(parent=self.guiparent, message=msg) return mapToAdd = self.results["vect_map"].GetVectMapName() mapToAddEx = grass.find_file(name=mapToAdd, element='vector', mapset=grass.gisenv()['MAPSET']) if not mapToAddEx["name"]: GMessage(parent=self.guiparent, message=msg) return addedMap = layer_name mapName, mapSet = ParseMapStr(addedMap) if mapSet != grass.gisenv()['MAPSET']: GMessage( parent=self.guiparent, message=_("Map can be saved only to currently set mapset")) return existsMap = grass.find_file(name=mapName, element='vector', mapset=grass.gisenv()['MAPSET']) if existsMap["name"]: dlg = wx.MessageDialog(parent=self.guiparent, message=_("Vector map %s already exists. " + "Do you want to overwrite it?") % (existsMap["fullname"]), caption=_("Overwrite vector map"), style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION | wx.CENTRE) ret = dlg.ShowModal() if ret == wx.ID_NO: dlg.Destroy() return dlg.Destroy() RunCommand("g.copy", overwrite=True, vector=[self.results["vect_map"].GetVectMapName(), mapName]) if len(self.giface.GetLayerList().GetLayersByName(mapName)) == 0: # TODO: get rid of insert cmd, cmd_colors = self.vnet_data.GetLayerStyle() cmd.insert(0, 'd.vect') cmd.append('map=%s' % mapName) self.giface.GetLayerList().AddLayer(ltype="vector", name=mapName, cmd=cmd, checked=True) if cmd_colors: layerStyleVnetColors = cmdlist_to_tuple(cmd_colors) RunCommand(layerStyleVnetColors[0], **layerStyleVnetColors[1]) else: self.giface.updateMap.emit(render=True, renderVector=True)
class ToolSwitcher: """Class handling switching tools in toolbar and custom toggle buttons.""" def __init__(self): self._groups = defaultdict(lambda: defaultdict(list)) self._toolsGroups = defaultdict(list) # emitted when tool is changed self.toggleToolChanged = Signal('ToolSwitcher.toggleToolChanged') def AddToolToGroup(self, group, toolbar, tool): """Adds tool from toolbar to group of exclusive tools. :param group: name of group (e.g. 'mouseUse') :param toolbar: instance of toolbar :param tool: id of a tool from the toolbar """ self._groups[group][toolbar].append(tool) self._toolsGroups[tool].append(group) def AddCustomToolToGroup(self, group, btnId, toggleHandler): """Adds custom tool from to group of exclusive tools (some toggle button). :param group: name of group (e.g. 'mouseUse') :param btnId: id of a tool (typically button) :param toggleHandler: handler to be called to switch the button """ self._groups[group]['custom'].append((btnId, toggleHandler)) self._toolsGroups[btnId].append(group) def RemoveCustomToolFromGroup(self, tool): """Removes custom tool from group. :param tool: id of the button """ if not tool in self._toolsGroups: return for group in self._toolsGroups[tool]: self._groups[group]['custom'] = \ [(bid, hdlr) for (bid, hdlr) in self._groups[group]['custom'] if bid != tool] def RemoveToolbarFromGroup(self, group, toolbar): """Removes toolbar from group. Before toolbar is destroyed, it must be removed from group, too. Otherwise we can expect some DeadObject errors. :param group: name of group (e.g. 'mouseUse') :param toolbar: instance of toolbar """ for tb in self._groups[group]: if tb == toolbar: del self._groups[group][tb] break def IsToolInGroup(self, tool, group): """Checks whether a tool is in a specified group. :param tool: tool id :param group: name of group (e.g. 'mouseUse') """ for group in self._toolsGroups[tool]: for tb in self._groups[group]: if tb.FindById(tool): return True return False def ToolChanged(self, tool): """When any tool/button is pressed, other tools from group must be unchecked. :param tool: id of a tool/button """ for group in self._toolsGroups[tool]: for tb in self._groups[group]: if tb == 'custom': for btnId, handler in self._groups[group][tb]: if btnId != tool: handler(False) else: for tl in self._groups[group][tb]: if tb.FindById(tl): # check if still exists if tl != tool: tb.ToggleTool(tl, False) else: tb.ToggleTool(tool, True) self.toggleToolChanged.emit(id=tool)
class RDigitController(wx.EvtHandler): def __init__(self, giface, mapWindow): wx.EvtHandler.__init__(self) self._giface = giface self._mapWindow = mapWindow self._thread = gThread() self._editedRaster = None self._backgroundRaster = None self._backupRasterName = None self._areas = None self._lines = None self._points = None self._all = [] self._drawing = False self._running = False self._drawColor = wx.GREEN self._drawTransparency = 100 self._graphicsType = 'area' self._currentCellValue = None self._currentWidthValue = None self._oldMouseUse = None self._oldCursor = None self.newRasterCreated = Signal('RDigitController:newRasterCreated') self.newFeatureCreated = Signal('RDigitController:newFeatureCreated') self.uploadMapCategories = Signal('RDigitController:uploadMapCategories') self.quitDigitizer = Signal('RDigitController:quitDigitizer') self.showNotification = Signal('RDigitController:showNotification') def _connectAll(self): self._mapWindow.mouseLeftDown.connect(self._start) self._mapWindow.mouseLeftUp.connect(self._addPoint) self._mapWindow.mouseRightUp.connect(self._finish) self._mapWindow.Unbind(wx.EVT_CONTEXT_MENU) def _disconnectAll(self): self._mapWindow.mouseLeftDown.disconnect(self._start) self._mapWindow.mouseLeftUp.disconnect(self._addPoint) self._mapWindow.mouseRightUp.connect(self._finish) self._mapWindow.Bind(wx.EVT_CONTEXT_MENU, self._mapWindow.OnContextMenu) def _start(self, x, y): if self._running: return if not self._editedRaster: GMessage(parent=self._mapWindow, message=_("Please select first the raster map")) return if not self._drawing: if self._graphicsType == 'area': item = self._areas.AddItem(coords=[]) item.SetPropertyVal('penName', 'pen1') self._all.append(item) elif self._graphicsType == 'line': item = self._lines.AddItem(coords=[]) item.SetPropertyVal('penName', 'pen1') self._all.append(item) elif self._graphicsType == 'point': item = self._points.AddItem(coords=[]) item.SetPropertyVal('penName', 'pen1') self._all.append(item) self._drawing = True def _addPoint(self, x, y): if self._running: return if not self._drawing: return if self._graphicsType == 'area': area = self._areas.GetItem(-1) coords = area.GetCoords() + [[x, y]] area.SetCoords(coords) self.showNotification.emit(text=_("Right click to finish area")) elif self._graphicsType == 'line': line = self._lines.GetItem(-1) coords = line.GetCoords() + [[x, y]] line.SetCoords(coords) self.showNotification.emit(text=_("Right click to finish line")) elif self._graphicsType == 'point': point = self._points.GetItem(-1) point.SetCoords([x, y]) self._finish(x, y) # draw self._mapWindow.ClearLines() self._lines.Draw(pdc=self._mapWindow.pdcTmp) self._areas.Draw(pdc=self._mapWindow.pdcTmp) self._points.Draw(pdc=self._mapWindow.pdcTmp) self._mapWindow.Refresh() def _finish(self, x, y): if self._running: return if self._graphicsType == 'point': item = self._points.GetItem(-1) elif self._graphicsType == 'area': item = self._areas.GetItem(-1) elif self._graphicsType == 'line': item = self._lines.GetItem(-1) self._drawing = False item.SetPropertyVal('brushName', 'done') item.AddProperty('cellValue') item.AddProperty('widthValue') item.SetPropertyVal('cellValue', self._currentCellValue) item.SetPropertyVal('widthValue', self._currentWidthValue) self.newFeatureCreated.emit() self._mapWindow.ClearLines() self._points.Draw(pdc=self._mapWindow.pdcTmp) self._areas.Draw(pdc=self._mapWindow.pdcTmp) self._lines.Draw(pdc=self._mapWindow.pdcTmp) self._mapWindow.Refresh() def SelectType(self, drawingType): if self._graphicsType and not drawingType: self._mapWindow.ClearLines(pdc=self._mapWindow.pdcTmp) self._mapWindow.mouse['end'] = self._mapWindow.mouse['begin'] # disconnect mouse events self._disconnectAll() self._mapWindow.SetNamedCursor(self._oldCursor) self._mapWindow.mouse['use'] = self._oldMouseUse elif self._graphicsType is None and drawingType: self._connectAll() # change mouse['box'] and pen to draw line during dragging # TODO: better solution for drawing this line self._mapWindow.mouse['use'] = None self._mapWindow.mouse['box'] = "line" self._mapWindow.pen = wx.Pen(colour='red', width=2, style=wx.SHORT_DASH) # change the cursor self._mapWindow.SetNamedCursor('pencil') self._graphicsType = drawingType def SetCellValue(self, value): self._currentCellValue = value def SetWidthValue(self, value): self._currentWidthValue = value def ChangeDrawColor(self, color): self._drawColor = color[:3] + (self._drawTransparency,) for each in (self._areas, self._lines, self._points): each.GetPen('pen1').SetColour(self._drawColor) each.GetBrush('done').SetColour(self._drawColor) self._mapWindow.UpdateMap(render=False) def Start(self): """register graphics to map window, connect required mouse signals. """ self._oldMouseUse = self._mapWindow.mouse['use'] self._oldCursor = self._mapWindow.GetNamedCursor() self._connectAll() # change mouse['box'] and pen to draw line during dragging # TODO: better solution for drawing this line self._mapWindow.mouse['use'] = None self._mapWindow.mouse['box'] = "line" self._mapWindow.pen = wx.Pen(colour='red', width=2, style=wx.SHORT_DASH) color = self._drawColor[:3] + (self._drawTransparency,) self._areas = self._mapWindow.RegisterGraphicsToDraw(graphicsType='polygon', mapCoords=True) self._areas.AddPen('pen1', wx.Pen(colour=color, width=2, style=wx.SOLID)) self._areas.AddBrush('done', wx.Brush(colour=color, style=wx.SOLID)) self._lines = self._mapWindow.RegisterGraphicsToDraw(graphicsType='line', mapCoords=True) self._lines.AddPen('pen1', wx.Pen(colour=color, width=2, style=wx.SOLID)) self._lines.AddBrush('done', wx.Brush(colour=color, style=wx.SOLID)) self._points = self._mapWindow.RegisterGraphicsToDraw(graphicsType='point', mapCoords=True) self._points.AddPen('pen1', wx.Pen(colour=color, width=2, style=wx.SOLID)) self._points.AddBrush('done', wx.Brush(colour=color, style=wx.SOLID)) # change the cursor self._mapWindow.SetNamedCursor('pencil') def Stop(self): dlg = wx.MessageDialog(self._mapWindow, _("Do you want to save edits?"), _("Save raster map edits"), wx.YES_NO) if dlg.ShowModal() == wx.ID_YES: self._running = True self._thread.Run(callable=self._exportRaster, ondone=lambda event: self._updateAndQuit()) else: self.quitDigitizer.emit() def Save(self): self._thread.Run(callable=self._exportRaster, ondone=lambda event: self._update()) def Undo(self): if len(self._all): removed = self._all.pop(-1) # try to remove from each, it fails quietly when theitem is not there self._areas.DeleteItem(removed) self._lines.DeleteItem(removed) self._points.DeleteItem(removed) self._drawing = False self._mapWindow.UpdateMap(render=False) def CleanUp(self, restore=True): """ :param restore: if restore previous cursor, mouse['use'] """ try: gcore.run_command('g.remove', type='rast', flags='f', name=self._backupRasterName, quiet=True) except CalledModuleError: pass self._mapWindow.ClearLines(pdc=self._mapWindow.pdcTmp) self._mapWindow.mouse['end'] = self._mapWindow.mouse['begin'] # disconnect mouse events if self._graphicsType: self._disconnectAll() # unregister self._mapWindow.UnregisterGraphicsToDraw(self._areas) self._mapWindow.UnregisterGraphicsToDraw(self._lines) self._mapWindow.UnregisterGraphicsToDraw(self._points) #self._registeredGraphics = None self._mapWindow.UpdateMap(render=False) if restore: # restore mouse['use'] and cursor to the state before measuring starts self._mapWindow.SetNamedCursor(self._oldCursor) self._mapWindow.mouse['use'] = self._oldMouseUse def _updateAndQuit(self): self._running = False self._mapWindow.UpdateMap(render=True) self.quitDigitizer.emit() def _update(self): self._running = False self._mapWindow.UpdateMap(render=True) def SelectOldMap(self, name): try: self._backupRaster(name) except ScriptError: GError(parent=self._mapWindow, message=_("Failed to create backup copy of edited raster map.")) return False self._editedRaster = name return True def SelectNewMap(self): dlg = NewRasterDialog(parent=self._mapWindow) if dlg.ShowModal() == wx.ID_OK: try: self._createNewMap(mapName=dlg.GetMapName(), backgroundMap=dlg.GetBackgroundMapName(), mapType=dlg.GetMapType()) except ScriptError: GError(parent=self._mapWindow, message=_("Failed to create new raster map.")) return False finally: dlg.Destroy() return True else: dlg.Destroy() return False def _createNewMap(self, mapName, backgroundMap, mapType): name = mapName.split('@')[0] background = backgroundMap.split('@')[0] types = {'CELL': 'int', 'FCELL': 'float', 'DCELL': 'double'} if background: back = background else: back = 'null()' try: grast.mapcalc(exp="{name} = {mtype}({back})".format(name=name, mtype=types[mapType], back=back), overwrite=True, quiet=True) if background: self._backgroundRaster = backgroundMap if mapType == 'CELL': values = gcore.read_command('r.describe', flags='1n', map=backgroundMap, quiet=True).strip() if values: self.uploadMapCategories.emit(values=values.split('\n')) except CalledModuleError: raise ScriptError self._backupRaster(name) name = name + '@' + gcore.gisenv()['MAPSET'] self._editedRaster = name self.newRasterCreated.emit(name=name) def _backupRaster(self, name): name = name.split('@')[0] backup = name + '_backupcopy_' + str(os.getpid()) try: gcore.run_command('g.copy', rast=[name, backup], quiet=True) except CalledModuleError: raise ScriptError self._backupRasterName = backup def _exportRaster(self): if not self._editedRaster: return if len(self._all) < 1: return tempRaster = 'tmp_rdigit_rast_' + str(os.getpid()) text = [] rastersToPatch = [] i = 0 lastCellValue = lastWidthValue = None evt = updateProgress(range=len(self._all), value=0, text=_("Rasterizing...")) wx.PostEvent(self, evt) lastCellValue = self._all[0].GetPropertyVal('cellValue') lastWidthValue = self._all[0].GetPropertyVal('widthValue') for item in self._all: if item.GetPropertyVal('widthValue') and \ (lastCellValue != item.GetPropertyVal('cellValue') or lastWidthValue != item.GetPropertyVal('widthValue')): if text: out = self._rasterize(text, lastWidthValue, tempRaster) rastersToPatch.append(out) text = [] self._writeItem(item, text) out = self._rasterize(text, item.GetPropertyVal('widthValue'), tempRaster) rastersToPatch.append(out) text = [] else: self._writeItem(item, text) lastCellValue = item.GetPropertyVal('cellValue') lastWidthValue = item.GetPropertyVal('widthValue') i += 1 evt = updateProgress(range=len(self._all), value=i, text=_("Rasterizing...")) wx.PostEvent(self, evt) if text: out = self._rasterize(text, item.GetPropertyVal('widthValue'), tempRaster) rastersToPatch.append(out) gcore.run_command('r.patch', input=sorted(rastersToPatch, reverse=True) + [self._backupRasterName], output=self._editedRaster, overwrite=True, quiet=True) gcore.run_command('g.remove', type='rast', flags='f', name=rastersToPatch + [tempRaster], quiet=True) try: if not self._backgroundRaster: table = UserSettings.Get(group='rasterLayer', key='colorTable', subkey='selection') gcore.run_command('r.colors', color=table, map=self._editedRaster, quiet=True) else: gcore.run_command('r.colors', map=self._editedRaster, raster=self._backgroundRaster, quiet=True) except CalledModuleError: GError(parent=self._mapWindow, message=_("Failed to set default color table for edited raster map")) def _writeFeature(self, item, vtype, text): coords = item.GetCoords() if vtype == 'P': coords = [coords] cellValue = item.GetPropertyVal('cellValue') record = '{vtype}\n'.format(vtype=vtype) for coord in coords: record += ' '.join([str(c) for c in coord]) record += '\n' record += '= {cellValue}\n'.format(cellValue=cellValue) text.append(record) def _writeItem(self, item, text): if item in self._areas.GetAllItems(): self._writeFeature(item, vtype='A', text=text) elif item in self._lines.GetAllItems(): self._writeFeature(item, vtype='L', text=text) elif item in self._points.GetAllItems(): self._writeFeature(item, vtype='P', text=text) def _rasterize(self, text, bufferDist, tempRaster): output = 'x' + str(uuid.uuid4())[:8] asciiFile = tempfile.NamedTemporaryFile(delete=False) asciiFile.write('\n'.join(text)) asciiFile.close() if bufferDist: gcore.run_command('r.in.poly', input=asciiFile.name, output=tempRaster, overwrite=True, quiet=True) gcore.run_command('r.grow', input=tempRaster, output=output, flags='m', radius=bufferDist, quiet=True) else: gcore.run_command('r.in.poly', input=asciiFile.name, output=output, quiet=True) os.unlink(asciiFile.name) return output
class VDigitToolbar(BaseToolbar): """Toolbar for digitization """ def __init__(self, parent, toolSwitcher, MapWindow, digitClass, giface, tools=[]): self.MapWindow = MapWindow self.Map = MapWindow.GetMap() # Map class instance self.tools = tools self.digitClass = digitClass BaseToolbar.__init__(self, parent, toolSwitcher) self.digit = None self._giface = giface self.fType = None # feature type for simple features editing self.editingStarted = Signal("VDigitToolbar.editingStarted") self.editingStopped = Signal("VDigitToolbar.editingStopped") self.editingBgMap = Signal("VDigitToolbar.editingBgMap") layerTree = self._giface.GetLayerTree() if layerTree: self.editingStarted.connect(layerTree.StartEditing) self.editingStopped.connect(layerTree.StopEditing) self.editingBgMap.connect(layerTree.SetBgMapForEditing) # currently selected map layer for editing (reference to MapLayer # instance) self.mapLayer = None # list of vector layers from Layer Manager (only in the current mapset) self.layers = [] self.comboid = self.combo = None self.undo = -1 self.redo = -1 # only one dialog can be open self.settingsDialog = None # create toolbars (two rows optionally) self.InitToolbar(self._toolbarData()) self._default = -1 # default action (digitize new point, line, etc.) self.action = {'desc': '', 'type': '', 'id': -1} self._currentAreaActionType = None # list of available vector maps self.UpdateListOfLayers(updateTool=True) for tool in ( 'addPoint', 'addLine', 'addBoundary', 'addCentroid', 'addArea', 'addVertex', 'deleteLine', 'deleteArea', 'displayAttr', 'displayCats', 'editLine', 'moveLine', 'moveVertex', 'removeVertex', 'additionalTools'): if hasattr(self, tool): tool = getattr(self, tool) self.toolSwitcher.AddToolToGroup( group='mouseUse', toolbar=self, tool=tool) else: Debug.msg(1, '%s skipped' % tool) # custom button for digitization of area/boundary/centroid # TODO: could this be somehow generalized? nAreaTools = 0 if self.tools and 'addBoundary' in self.tools: nAreaTools += 1 if self.tools and 'addCentroid' in self.tools: nAreaTools += 1 if self.tools and 'addArea' in self.tools: nAreaTools += 1 if nAreaTools != 1: self.areaButton = self.CreateSelectionButton( _("Select area/boundary/centroid tool")) self.areaButtonId = self.InsertControl(5, self.areaButton) self.areaButton.Bind(wx.EVT_BUTTON, self.OnAddAreaMenu) # realize toolbar self.Realize() # workaround for Mac bug. May be fixed by 2.8.8, but not before then. if self.combo: self.combo.Hide() self.combo.Show() # disable undo/redo if self.undo > 0: self.EnableTool(self.undo, False) if self.redo > 0: self.EnableTool(self.redo, False) self.FixSize(width=105) def _toolbarData(self): """Toolbar data """ data = [] self.icons = { 'addPoint': MetaIcon(img='point-create', label=_('Digitize new point'), desc=_('Left: new point')), 'addLine': MetaIcon(img='line-create', label=_('Digitize new line'), desc=_('Left: new point; Ctrl+Left: undo last point; Right: close line')), 'addBoundary': MetaIcon(img='boundary-create', label=_('Digitize new boundary'), desc=_('Left: new point; Ctrl+Left: undo last point; Right: close line')), 'addCentroid': MetaIcon(img='centroid-create', label=_('Digitize new centroid'), desc=_('Left: new point')), 'addArea': MetaIcon(img='polygon-create', label=_('Digitize new area (boundary without category)'), desc=_('Left: new point')), 'addVertex': MetaIcon(img='vertex-create', label=_('Add new vertex to line or boundary'), desc=_('Left: Select; Ctrl+Left: Unselect; Right: Confirm')), 'deleteLine': MetaIcon(img='line-delete', label=_('Delete selected point(s), line(s), boundary(ies) or centroid(s)'), desc=_('Left: Select; Ctrl+Left: Unselect; Right: Confirm')), 'deleteArea': MetaIcon(img='polygon-delete', label=_('Delete selected area(s)'), desc=_('Left: Select; Ctrl+Left: Unselect; Right: Confirm')), 'displayAttr': MetaIcon(img='attributes-display', label=_('Display/update attributes'), desc=_('Left: Select')), 'displayCats': MetaIcon(img='cats-display', label=_('Display/update categories'), desc=_('Left: Select')), 'editLine': MetaIcon(img='line-edit', label=_('Edit selected line/boundary'), desc=_('Left: new point; Ctrl+Left: undo last point; Right: close line')), 'moveLine': MetaIcon(img='line-move', label=_('Move selected point(s), line(s), boundary(ies) or centroid(s)'), desc=_('Left: Select; Ctrl+Left: Unselect; Right: Confirm')), 'moveVertex': MetaIcon(img='vertex-move', label=_('Move selected vertex'), desc=_('Left: Select; Ctrl+Left: Unselect; Right: Confirm')), 'removeVertex': MetaIcon(img='vertex-delete', label=_('Remove selected vertex'), desc=_('Left: Select; Ctrl+Left: Unselect; Right: Confirm')), 'settings': BaseIcons['settings'].SetLabel(_('Digitization settings')), 'quit': BaseIcons['quit'].SetLabel(label=_('Quit digitizer'), desc=_('Quit digitizer and save changes')), 'help': BaseIcons['help'].SetLabel(label=_('Vector Digitizer manual'), desc=_('Show Vector Digitizer manual')), 'additionalTools': MetaIcon(img='tools', label=_('Additional tools ' '(copy, flip, connect, etc.)'), desc=_('Left: Select; Ctrl+Left: Unselect; Right: Confirm')), 'undo': MetaIcon(img='undo', label=_('Undo'), desc=_('Undo previous changes')), 'redo': MetaIcon(img='redo', label=_('Redo'), desc=_('Redo previous changes')), } if not self.tools or 'selector' in self.tools: data.append((None, )) if not self.tools or 'addPoint' in self.tools: data.append(("addPoint", self.icons["addPoint"], self.OnAddPoint, wx.ITEM_CHECK)) if not self.tools or 'addLine' in self.tools: data.append(("addLine", self.icons["addLine"], self.OnAddLine, wx.ITEM_CHECK)) if not self.tools or 'addArea' in self.tools: data.append(("addArea", self.icons["addArea"], self.OnAddAreaTool, wx.ITEM_CHECK)) if not self.tools or 'deleteLine' in self.tools: data.append(("deleteLine", self.icons["deleteLine"], self.OnDeleteLine, wx.ITEM_CHECK)) if not self.tools or 'deleteArea' in self.tools: data.append(("deleteArea", self.icons["deleteArea"], self.OnDeleteArea, wx.ITEM_CHECK)) if not self.tools or 'moveVertex' in self.tools: data.append(("moveVertex", self.icons["moveVertex"], self.OnMoveVertex, wx.ITEM_CHECK)) if not self.tools or 'addVertex' in self.tools: data.append(("addVertex", self.icons["addVertex"], self.OnAddVertex, wx.ITEM_CHECK)) if not self.tools or 'removeVertex' in self.tools: data.append(("removeVertex", self.icons["removeVertex"], self.OnRemoveVertex, wx.ITEM_CHECK)) if not self.tools or 'editLine' in self.tools: data.append(("editLine", self.icons["editLine"], self.OnEditLine, wx.ITEM_CHECK)) if not self.tools or 'moveLine' in self.tools: data.append(("moveLine", self.icons["moveLine"], self.OnMoveLine, wx.ITEM_CHECK)) if not self.tools or 'displayCats' in self.tools: data.append(("displayCats", self.icons["displayCats"], self.OnDisplayCats, wx.ITEM_CHECK)) if not self.tools or 'displayAttr' in self.tools: data.append(("displayAttr", self.icons["displayAttr"], self.OnDisplayAttr, wx.ITEM_CHECK)) if not self.tools or 'additionalSelf.Tools' in self.tools: data.append(("additionalTools", self.icons["additionalTools"], self.OnAdditionalToolMenu, wx.ITEM_CHECK)) if not self.tools or 'undo' in self.tools or \ 'redo' in self.tools: data.append((None, )) if not self.tools or 'undo' in self.tools: data.append(("undo", self.icons["undo"], self.OnUndo)) if not self.tools or 'redo' in self.tools: data.append(("redo", self.icons["redo"], self.OnRedo)) if not self.tools or 'settings' in self.tools or \ 'help' in self.tools or \ 'quit' in self.tools: data.append((None, )) if not self.tools or 'settings' in self.tools: data.append(("settings", self.icons["settings"], self.OnSettings)) if not self.tools or 'help' in self.tools: data.append(("help", self.icons["help"], self.OnHelp)) if not self.tools or 'quit' in self.tools: data.append(("quit", self.icons["quit"], self.OnExit)) return self._getToolbarData(data) def OnTool(self, event): """Tool selected -> untoggles previusly selected tool in toolbar""" Debug.msg(3, "VDigitToolbar.OnTool(): id = %s" % event.GetId()) # set cursor self.MapWindow.SetNamedCursor('cross') self.MapWindow.mouse['box'] = 'point' self.MapWindow.mouse['use'] = 'pointer' aId = self.action.get('id', -1) BaseToolbar.OnTool(self, event) # clear tmp canvas if self.action['id'] != aId or aId == -1: self.MapWindow.polycoords = [] self.MapWindow.ClearLines(pdc=self.MapWindow.pdcTmp) if self.digit and \ len(self.MapWindow.digit.GetDisplay().GetSelected()) > 0: # cancel action self.MapWindow.OnMiddleDown(None) # set no action if self.action['id'] == -1: self.action = {'desc': '', 'type': '', 'id': -1} # set focus self.MapWindow.SetFocus() def OnAddPoint(self, event): """Add point to the vector map Laier""" Debug.msg(2, "VDigitToolbar.OnAddPoint()") self.action = {'desc': "addLine", 'type': "point", 'id': self.addPoint} self.MapWindow.mouse['box'] = 'point' def OnAddLine(self, event): """Add line to the vector map layer""" Debug.msg(2, "VDigitToolbar.OnAddLine()") self.action = {'desc': "addLine", 'type': "line", 'id': self.addLine} self.MapWindow.mouse['box'] = 'line' # self.MapWindow.polycoords = [] # reset temp line def OnAddBoundary(self, event): """Add boundary to the vector map layer""" Debug.msg(2, "VDigitToolbar.OnAddBoundary()") self._toggleAreaIfNeeded() # reset temp line if self.action['desc'] != 'addLine' or \ self.action['type'] != 'boundary': self.MapWindow.polycoords = [] # update icon and tooltip self.SetToolNormalBitmap(self.addArea, self.icons[ 'addBoundary'].GetBitmap()) self.SetToolShortHelp(self.addArea, self.icons['addBoundary'].GetLabel()) # set action self.action = {'desc': "addLine", 'type': "boundary", 'id': self.addArea} self.MapWindow.mouse['box'] = 'line' self._currentAreaActionType = 'boundary' def OnAddCentroid(self, event): """Add centroid to the vector map layer""" Debug.msg(2, "VDigitToolbar.OnAddCentroid()") self._toggleAreaIfNeeded() # update icon and tooltip self.SetToolNormalBitmap(self.addArea, self.icons[ 'addCentroid'].GetBitmap()) self.SetToolShortHelp(self.addArea, self.icons['addCentroid'].GetLabel()) # set action self.action = {'desc': "addLine", 'type': "centroid", 'id': self.addArea} self.MapWindow.mouse['box'] = 'point' self._currentAreaActionType = 'centroid' def OnAddArea(self, event): """Add area to the vector map layer""" Debug.msg(2, "VDigitToolbar.OnAddArea()") self._toggleAreaIfNeeded() # update icon and tooltip self.SetToolNormalBitmap( self.addArea, self.icons['addArea'].GetBitmap()) self.SetToolShortHelp(self.addArea, self.icons['addArea'].GetLabel()) # set action self.action = {'desc': "addLine", 'type': "area", 'id': self.addArea} self.MapWindow.mouse['box'] = 'line' self._currentAreaActionType = 'area' def _toggleAreaIfNeeded(self): """In some cases, the area tool is not toggled, we have to do it manually.""" if not self.GetToolState(self.addArea): self.ToggleTool(self.addArea, True) self.toolSwitcher.ToolChanged(self.addArea) def OnAddAreaTool(self, event): """Area tool activated.""" Debug.msg(2, "VDigitToolbar.OnAddAreaTool()") # we need the previous id if not self._currentAreaActionType or self._currentAreaActionType == 'area': # default action self.OnAddArea(event) elif self._currentAreaActionType == 'boundary': self.OnAddBoundary(event) elif self._currentAreaActionType == 'centroid': self.OnAddCentroid(event) def OnAddAreaMenu(self, event): """Digitize area menu (add area/boundary/centroid)""" menuItems = [] if not self.tools or 'addArea' in self.tools: menuItems.append((self.icons["addArea"], self.OnAddArea)) if not self.fType and not self.tools or 'addBoundary' in self.tools: menuItems.append((self.icons["addBoundary"], self.OnAddBoundary)) if not self.fType and not self.tools or 'addCentroid' in self.tools: menuItems.append((self.icons["addCentroid"], self.OnAddCentroid)) self._onMenu(menuItems) def OnExit(self, event=None): """Quit digitization tool""" # stop editing of the currently selected map layer if self.mapLayer: self.StopEditing() # close dialogs if still open if self.settingsDialog: self.settingsDialog.OnCancel(None) # set default mouse settings self.parent.GetMapToolbar().SelectDefault() self.MapWindow.polycoords = [] # TODO: replace this by binding wx event in parent (or use signals...) if not self.parent.IsStandalone(): # disable the toolbar self.parent.RemoveToolbar("vdigit") else: self.parent.Close() def OnMoveVertex(self, event): """Move line vertex""" Debug.msg(2, "Digittoolbar.OnMoveVertex():") self.action = {'desc': "moveVertex", 'id': self.moveVertex} self.MapWindow.mouse['box'] = 'point' def OnAddVertex(self, event): """Add line vertex""" Debug.msg(2, "Digittoolbar.OnAddVertex():") self.action = {'desc': "addVertex", 'id': self.addVertex} self.MapWindow.mouse['box'] = 'point' def OnRemoveVertex(self, event): """Remove line vertex""" Debug.msg(2, "Digittoolbar.OnRemoveVertex():") self.action = {'desc': "removeVertex", 'id': self.removeVertex} self.MapWindow.mouse['box'] = 'point' def OnEditLine(self, event): """Edit line""" Debug.msg(2, "Digittoolbar.OnEditLine():") self.action = {'desc': "editLine", 'id': self.editLine} self.MapWindow.mouse['box'] = 'line' def OnMoveLine(self, event): """Move line""" Debug.msg(2, "Digittoolbar.OnMoveLine():") self.action = {'desc': "moveLine", 'id': self.moveLine} self.MapWindow.mouse['box'] = 'box' def OnDeleteLine(self, event): """Delete line""" Debug.msg(2, "Digittoolbar.OnDeleteLine():") self.action = {'desc': "deleteLine", 'id': self.deleteLine} self.MapWindow.mouse['box'] = 'box' def OnDeleteArea(self, event): """Delete Area""" Debug.msg(2, "Digittoolbar.OnDeleteArea():") self.action = {'desc': "deleteArea", 'id': self.deleteArea} self.MapWindow.mouse['box'] = 'box' def OnDisplayCats(self, event): """Display/update categories""" Debug.msg(2, "Digittoolbar.OnDisplayCats():") self.action = {'desc': "displayCats", 'id': self.displayCats} self.MapWindow.mouse['box'] = 'point' def OnDisplayAttr(self, event): """Display/update attributes""" Debug.msg(2, "Digittoolbar.OnDisplayAttr():") self.action = {'desc': "displayAttrs", 'id': self.displayAttr} self.MapWindow.mouse['box'] = 'point' def OnUndo(self, event): """Undo previous changes""" self.digit.Undo() event.Skip() def OnRedo(self, event): """Undo previous changes""" self.digit.Undo(level=1) event.Skip() def EnableUndo(self, enable=True): """Enable 'Undo' in toolbar :param enable: False for disable """ self._enableTool(self.undo, enable) def EnableRedo(self, enable=True): """Enable 'Redo' in toolbar :param enable: False for disable """ self._enableTool(self.redo, enable) def _enableTool(self, tool, enable): if not self.FindById(tool): return if enable: if self.GetToolEnabled(tool) is False: self.EnableTool(tool, True) else: if self.GetToolEnabled(tool) is True: self.EnableTool(tool, False) def GetAction(self, type='desc'): """Get current action info""" return self.action.get(type, '') def OnSettings(self, event): """Show settings dialog""" if self.digit is None: try: self.digit = self.MapWindow.digit = self.digitClass( mapwindow=self.MapWindow) except SystemExit: self.digit = self.MapWindow.digit = None if not self.settingsDialog: self.settingsDialog = VDigitSettingsDialog( parent=self.parent, giface=self._giface) self.settingsDialog.Show() def OnHelp(self, event): """Show digitizer help page in web browser""" self._giface.Help('wxGUI.vdigit') def OnAdditionalToolMenu(self, event): """Menu for additional tools""" point = wx.GetMousePosition() toolMenu = wx.Menu() for label, itype, handler, desc in ( (_('Break selected lines/boundaries at intersection'), wx.ITEM_CHECK, self.OnBreak, "breakLine"), (_('Connect selected lines/boundaries'), wx.ITEM_CHECK, self.OnConnect, "connectLine"), (_('Copy categories'), wx.ITEM_CHECK, self.OnCopyCats, "copyCats"), (_('Copy features from (background) vector map'), wx.ITEM_CHECK, self.OnCopy, "copyLine"), (_('Copy attributes'), wx.ITEM_CHECK, self.OnCopyAttrb, "copyAttrs"), (_('Feature type conversion'), wx.ITEM_CHECK, self.OnTypeConversion, "typeConv"), (_('Flip selected lines/boundaries'), wx.ITEM_CHECK, self.OnFlip, "flipLine"), (_('Merge selected lines/boundaries'), wx.ITEM_CHECK, self.OnMerge, "mergeLine"), (_('Snap selected lines/boundaries (only to nodes)'), wx.ITEM_CHECK, self.OnSnap, "snapLine"), (_('Split line/boundary'), wx.ITEM_CHECK, self.OnSplitLine, "splitLine"), (_('Query features'), wx.ITEM_CHECK, self.OnQuery, "queryLine"), (_('Z bulk-labeling of 3D lines'), wx.ITEM_CHECK, self.OnZBulk, "zbulkLine")): # Add items to the menu item = wx.MenuItem(parentMenu=toolMenu, id=wx.ID_ANY, text=label, kind=itype) toolMenu.AppendItem(item) self.MapWindow.Bind(wx.EVT_MENU, handler, item) if self.action['desc'] == desc: item.Check(True) # Popup the menu. If an item is selected then its handler # will be called before PopupMenu returns. self.MapWindow.PopupMenu(toolMenu) toolMenu.Destroy() if self.action['desc'] == 'addPoint': self.ToggleTool(self.additionalTools, False) def OnCopy(self, event): """Copy selected features from (background) vector map""" if not self.digit: GError(_("No vector map open for editing."), self.parent) return # select background map dlg = VectorDialog(self.parent, title=_("Select background vector map"), layerTree=self._giface.GetLayerTree()) if dlg.ShowModal() != wx.ID_OK: dlg.Destroy() return mapName = dlg.GetName(full=True) dlg.Destroy() # close open background map if any bgMap = UserSettings.Get(group='vdigit', key='bgmap', subkey='value', settings_type='internal') if bgMap: self.digit.CloseBackgroundMap() self.editingBgMap.emit(mapName=bgMap, unset=True) # open background map for reading UserSettings.Set(group='vdigit', key='bgmap', subkey='value', value=str(mapName), settings_type='internal') self.digit.OpenBackgroundMap(mapName) self.editingBgMap.emit(mapName=mapName) if self.action['desc'] == 'copyLine': # select previous action self.ToggleTool(self.addPoint, True) self.ToggleTool(self.additionalTools, False) self.OnAddPoint(event) return Debug.msg(2, "Digittoolbar.OnCopy():") self.action = {'desc': "copyLine", 'id': self.additionalTools} self.MapWindow.mouse['box'] = 'box' def OnSplitLine(self, event): """Split line""" if self.action['desc'] == 'splitLine': # select previous action self.ToggleTool(self.addPoint, True) self.ToggleTool(self.additionalTools, False) self.OnAddPoint(event) return Debug.msg(2, "Digittoolbar.OnSplitLine():") self.action = {'desc': "splitLine", 'id': self.additionalTools} self.MapWindow.mouse['box'] = 'point' def OnCopyCats(self, event): """Copy categories""" if self.action['desc'] == 'copyCats': # select previous action self.ToggleTool(self.addPoint, True) self.ToggleTool(self.copyCats, False) self.OnAddPoint(event) return Debug.msg(2, "Digittoolbar.OnCopyCats():") self.action = {'desc': "copyCats", 'id': self.additionalTools} self.MapWindow.mouse['box'] = 'point' def OnCopyAttrb(self, event): """Copy attributes""" if self.action['desc'] == 'copyAttrs': # select previous action self.ToggleTool(self.addPoint, True) self.ToggleTool(self.copyCats, False) self.OnAddPoint(event) return Debug.msg(2, "Digittoolbar.OnCopyAttrb():") self.action = {'desc': "copyAttrs", 'id': self.additionalTools} self.MapWindow.mouse['box'] = 'point' def OnFlip(self, event): """Flip selected lines/boundaries""" if self.action['desc'] == 'flipLine': # select previous action self.ToggleTool(self.addPoint, True) self.ToggleTool(self.additionalTools, False) self.OnAddPoint(event) return Debug.msg(2, "Digittoolbar.OnFlip():") self.action = {'desc': "flipLine", 'id': self.additionalTools} self.MapWindow.mouse['box'] = 'box' def OnMerge(self, event): """Merge selected lines/boundaries""" if self.action['desc'] == 'mergeLine': # select previous action self.ToggleTool(self.addPoint, True) self.ToggleTool(self.additionalTools, False) self.OnAddPoint(event) return Debug.msg(2, "Digittoolbar.OnMerge():") self.action = {'desc': "mergeLine", 'id': self.additionalTools} self.MapWindow.mouse['box'] = 'box' def OnBreak(self, event): """Break selected lines/boundaries""" if self.action['desc'] == 'breakLine': # select previous action self.ToggleTool(self.addPoint, True) self.ToggleTool(self.additionalTools, False) self.OnAddPoint(event) return Debug.msg(2, "Digittoolbar.OnBreak():") self.action = {'desc': "breakLine", 'id': self.additionalTools} self.MapWindow.mouse['box'] = 'box' def OnSnap(self, event): """Snap selected features""" if self.action['desc'] == 'snapLine': # select previous action self.ToggleTool(self.addPoint, True) self.ToggleTool(self.additionalTools, False) self.OnAddPoint(event) return Debug.msg(2, "Digittoolbar.OnSnap():") self.action = {'desc': "snapLine", 'id': self.additionalTools} self.MapWindow.mouse['box'] = 'box' def OnConnect(self, event): """Connect selected lines/boundaries""" if self.action['desc'] == 'connectLine': # select previous action self.ToggleTool(self.addPoint, True) self.ToggleTool(self.additionalTools, False) self.OnAddPoint(event) return Debug.msg(2, "Digittoolbar.OnConnect():") self.action = {'desc': "connectLine", 'id': self.additionalTools} self.MapWindow.mouse['box'] = 'box' def OnQuery(self, event): """Query selected lines/boundaries""" if self.action['desc'] == 'queryLine': # select previous action self.ToggleTool(self.addPoint, True) self.ToggleTool(self.additionalTools, False) self.OnAddPoint(event) return Debug.msg( 2, "Digittoolbar.OnQuery(): %s" % UserSettings.Get( group='vdigit', key='query', subkey='selection')) self.action = {'desc': "queryLine", 'id': self.additionalTools} self.MapWindow.mouse['box'] = 'box' def OnZBulk(self, event): """Z bulk-labeling selected lines/boundaries""" if not self.digit.IsVector3D(): GError(parent=self.parent, message=_("Vector map is not 3D. Operation canceled.")) return if self.action['desc'] == 'zbulkLine': # select previous action self.ToggleTool(self.addPoint, True) self.ToggleTool(self.additionalTools, False) self.OnAddPoint(event) return Debug.msg(2, "Digittoolbar.OnZBulk():") self.action = {'desc': "zbulkLine", 'id': self.additionalTools} self.MapWindow.mouse['box'] = 'line' def OnTypeConversion(self, event): """Feature type conversion Supported conversions: - point <-> centroid - line <-> boundary """ if self.action['desc'] == 'typeConv': # select previous action self.ToggleTool(self.addPoint, True) self.ToggleTool(self.additionalTools, False) self.OnAddPoint(event) return Debug.msg(2, "Digittoolbar.OnTypeConversion():") self.action = {'desc': "typeConv", 'id': self.additionalTools} self.MapWindow.mouse['box'] = 'box' def OnSelectMap(self, event): """Select vector map layer for editing If there is a vector map layer already edited, this action is firstly terminated. The map layer is closed. After this the selected map layer activated for editing. """ if event.GetSelection() == 0: # create new vector map layer if self.mapLayer: openVectorMap = self.mapLayer.GetName( fullyQualified=False)['name'] else: openVectorMap = None dlg = CreateNewVector(self.parent, exceptMap=openVectorMap, giface=self._giface, cmd=(('v.edit', {'tool': 'create'}, 'map')), disableAdd=True) if dlg and dlg.GetName(): # add layer to map layer tree if self._giface.GetLayerTree(): mapName = dlg.GetName() + '@' + grass.gisenv()['MAPSET'] self._giface.GetLayerList().AddLayer( ltype='vector', name=mapName, checked=True, cmd=['d.vect', 'map=%s' % mapName]) vectLayers = self.UpdateListOfLayers(updateTool=True) selection = vectLayers.index(mapName) # create table ? if dlg.IsChecked('table'): # TODO: replace this by signal # also note that starting of tools such as atm, iclass, # plots etc. should be handled in some better way # than starting randomly from mapdisp and lmgr lmgr = self.parent.GetLayerManager() if lmgr: lmgr.OnShowAttributeTable(None, selection='table') dlg.Destroy() else: self.combo.SetValue(_('Select vector map')) if dlg: dlg.Destroy() return else: selection = event.GetSelection() - 1 # first option is 'New vector map' # skip currently selected map if self.layers[selection] == self.mapLayer: return if self.mapLayer: # deactive map layer for editing self.StopEditing() # select the given map layer for editing self.StartEditing(self.layers[selection]) event.Skip() def StartEditing(self, mapLayer): """Start editing selected vector map layer. :param mapLayer: MapLayer to be edited """ # check if topology is available (skip for hidden - temporary # maps, see iclass for details) if not mapLayer.IsHidden() and grass.vector_info( mapLayer.GetName())['level'] != 2: dlg = wx.MessageDialog( parent=self.MapWindow, message=_( "Topology for vector map <%s> is not available. " "Topology is required by digitizer.\nDo you want to " "rebuild topology (takes some time) and open the vector map " "for editing?") % mapLayer.GetName(), caption=_("Digitizer error"), style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION | wx.CENTRE) if dlg.ShowModal() == wx.ID_YES: RunCommand('v.build', map=mapLayer.GetName()) else: return # deactive layer self.Map.ChangeLayerActive(mapLayer, False) # clean map canvas self.MapWindow.EraseMap() # unset background map if needed if mapLayer: if UserSettings.Get( group='vdigit', key='bgmap', subkey='value', settings_type='internal') == mapLayer.GetName(): UserSettings.Set( group='vdigit', key='bgmap', subkey='value', value='', settings_type='internal') self.parent.SetStatusText(_("Please wait, " "opening vector map <%s> for editing...") % mapLayer.GetName(), 0) self.MapWindow.pdcVector = wx.PseudoDC() self.digit = self.MapWindow.digit = self.digitClass( mapwindow=self.MapWindow) self.mapLayer = mapLayer # open vector map (assume that 'hidden' map layer is temporary vector # map) if self.digit.OpenMap( mapLayer.GetName(), tmp=mapLayer.IsHidden()) is None: self.mapLayer = None self.StopEditing() return False # check feature type (only for OGR layers) self.fType = self.digit.GetFeatureType() self.EnableAll() self.EnableUndo(False) self.EnableRedo(False) if self.fType == 'point': for tool in (self.addLine, self.addArea, self.moveVertex, self.addVertex, self.removeVertex, self.editLine): self.EnableTool(tool, False) elif self.fType == 'linestring': for tool in (self.addPoint, self.addArea): self.EnableTool(tool, False) elif self.fType == 'polygon': for tool in (self.addPoint, self.addLine): self.EnableTool(tool, False) elif self.fType: GError( parent=self, message=_( "Unsupported feature type '%(type)s'. Unable to edit " "OGR layer <%(layer)s>.") % {'type': self.fType, 'layer': mapLayer.GetName()}) self.digit.CloseMap() self.mapLayer = None self.StopEditing() return False # update toolbar if self.combo: self.combo.SetValue(mapLayer.GetName()) if 'map' in self.parent.toolbars: self.parent.toolbars['map'].combo.SetValue(_('Vector digitizer')) # here was dead code to enable vdigit button in toolbar # with if to ignore iclass # some signal (DigitizerStarted) can be emitted here Debug.msg( 4, "VDigitToolbar.StartEditing(): layer=%s" % mapLayer.GetName()) # change cursor if self.MapWindow.mouse['use'] == 'pointer': self.MapWindow.SetNamedCursor('cross') if not self.MapWindow.resize: self.MapWindow.UpdateMap(render=True) # respect opacity opacity = mapLayer.GetOpacity() if opacity < 1.0: alpha = int(opacity * 255) self.digit.GetDisplay().UpdateSettings(alpha=alpha) # emit signal layerTree = self._giface.GetLayerTree() if layerTree: item = layerTree.FindItemByData('maplayer', self.mapLayer) else: item = None self.editingStarted.emit( vectMap=mapLayer.GetName(), digit=self.digit, layerItem=item) return True def StopEditing(self): """Stop editing of selected vector map layer. :return: True on success :return: False on failure """ item = None if self.combo: self.combo.SetValue(_('Select vector map')) # save changes if self.mapLayer: Debug.msg( 4, "VDigitToolbar.StopEditing(): layer=%s" % self.mapLayer.GetName()) if UserSettings.Get(group='vdigit', key='saveOnExit', subkey='enabled') is False: if self.digit.GetUndoLevel() > -1: dlg = wx.MessageDialog( parent=self.parent, message=_( "Do you want to save changes " "in vector map <%s>?") % self.mapLayer.GetName(), caption=_("Save changes?"), style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION) if dlg.ShowModal() == wx.ID_NO: # revert changes self.digit.Undo(0) dlg.Destroy() self.parent.SetStatusText(_("Please wait, " "closing and rebuilding topology of " "vector map <%s>...") % self.mapLayer.GetName(), 0) self.digit.CloseMap() # close open background map if any bgMap = UserSettings.Get( group='vdigit', key='bgmap', subkey='value', settings_type='internal') if bgMap: self.digit.CloseBackgroundMap() self.editingBgMap.emit(mapName=bgMap, unset=True) self._giface.GetProgress().SetValue(0) self._giface.WriteCmdLog( _("Editing of vector map <%s> successfully finished") % self.mapLayer.GetName(), notification=Notification.HIGHLIGHT) # re-active layer layerTree = self._giface.GetLayerTree() if layerTree: item = layerTree.FindItemByData('maplayer', self.mapLayer) if item and layerTree.IsItemChecked(item): self.Map.ChangeLayerActive(self.mapLayer, True) # change cursor self.MapWindow.SetNamedCursor('default') self.MapWindow.pdcVector = None # close dialogs for dialog in ('attributes', 'category'): if self.parent.dialogs[dialog]: self.parent.dialogs[dialog].Close() self.parent.dialogs[dialog] = None self.digit = None self.MapWindow.digit = None self.editingStopped.emit(layerItem=item) self.mapLayer = None self.MapWindow.redrawAll = True return True def UpdateListOfLayers(self, updateTool=False): """Update list of available vector map layers. This list consists only editable layers (in the current mapset) :param updateTool: True to update also toolbar :type updateTool: bool """ Debug.msg(4, "VDigitToolbar.UpdateListOfLayers(): updateTool=%d" % updateTool) layerNameSelected = None # name of currently selected layer if self.mapLayer: layerNameSelected = self.mapLayer.GetName() # select vector map layer in the current mapset layerNameList = [] self.layers = self.Map.GetListOfLayers(ltype="vector", mapset=grass.gisenv()['MAPSET']) for layer in self.layers: if not layer.name in layerNameList: # do not duplicate layer layerNameList.append(layer.GetName()) if updateTool: # update toolbar if not self.mapLayer: value = _('Select vector map') else: value = layerNameSelected if not self.comboid: if not self.tools or 'selector' in self.tools: self.combo = wx.ComboBox( self, id=wx.ID_ANY, value=value, choices=[_('New vector map'), ] + layerNameList, size=(80, -1), style=wx.CB_READONLY) self.comboid = self.InsertControl(0, self.combo) self.parent.Bind( wx.EVT_COMBOBOX, self.OnSelectMap, self.comboid) else: self.combo.SetItems([_('New vector map'), ] + layerNameList) self.Realize() return layerNameList def GetLayer(self): """Get selected layer for editing -- MapLayer instance""" return self.mapLayer
class LayersList(TreeListCtrl, listmix.ListCtrlAutoWidthMixin): def __init__(self, parent, web_service, style, pos=wx.DefaultPosition): """List of layers and styles available in capabilities file """ self.parent = parent self.ws = web_service TreeListCtrl.__init__(self, parent=parent, id=wx.ID_ANY, style=style) # setup mixins listmix.ListCtrlAutoWidthMixin.__init__(self) if self.ws != 'OnEarth': self.AddColumn(_('Name')) self.AddColumn(_('Type')) else: self.AddColumn(_('Layer name')) self.SetMainColumn(0) # column with the tree self.setResizeColumn(0) self.root = None self.Bind(wx.EVT_TREE_SEL_CHANGING, self.OnListSelChanging) self.layerSelected = Signal('LayersList.layerSelected') def LoadData(self, cap=None): """Load data into list """ # detete first all items self.DeleteAllItems() if not cap: return def AddLayerChildrenToTree(parent_layer, parent_item): """Recursive function which adds all capabilities layers/styles to the LayersList. """ def gettitle(layer): """Helper function""" if layer.GetLayerData('title') is not None: layer_title = layer.GetLayerData('title') elif layer.GetLayerData('name') is not None: layer_title = layer.GetLayerData('name') else: layer_title = str(layer.GetId()) return layer_title def addlayer(layer, item): if self.ws != 'OnEarth': self.SetItemText(item, _('layer'), 1) styles = layer.GetLayerData('styles') def_st = None for st in styles: if st['name']: style_name = st['name'] else: continue if st['title']: style_name = st['title'] if st['isDefault']: def_st = st style_item = self.AppendItem(item, style_name) if self.ws != 'OnEarth': self.SetItemText(style_item, _('style'), 1) self.SetPyData(style_item, {'type': 'style', 'layer': layer, # it is parent layer of style 'style': st}) self.SetPyData(item, {'type': 'layer', # is it layer or style? 'layer': layer, # Layer instance from web_services.cap_interface 'style': def_st}) # layer can have assigned default style if parent_layer is None: parent_layer = cap.GetRootLayer() layer_title = gettitle(parent_layer) parent_item = self.AddRoot(layer_title) addlayer(parent_layer, parent_item) for layer in parent_layer.GetChildren(): item = self.AppendItem(parent_item, gettitle(layer)) addlayer(layer, item) AddLayerChildrenToTree(layer, item) AddLayerChildrenToTree(None, None) # self.ExpandAll(self.GetRootItem()) def GetSelectedLayers(self): """Get selected layers/styles in LayersList :return: dict with these items: * 'name' : layer name used for request if it is style, it is name of parent layer * 'title' : layer title * 'style' : {'name' : 'style name', title : 'style title'} * 'cap_intf_l' : \*Layer instance from web_services.cap_interface """ sel_layers = self.GetSelections() sel_layers_dict = [] for s in sel_layers: try: layer = self.GetPyData(s)['layer'] except ValueError: continue sel_layers_dict.append({ 'name': layer.GetLayerData('name'), 'title': layer.GetLayerData('title'), 'style': self.GetPyData(s)['style'], 'cap_intf_l': layer }) return sel_layers_dict def OnListSelChanging(self, event): """Do not allow selecting items, which cannot be requested from server. """ def _emitSelected(layer): title = layer.GetLayerData('title') self.layerSelected.emit(title=title) def _selectRequestableChildren(item, list_to_check, items_to_sel): self.Expand(item) child_item, cookie = self.GetFirstChild(item) while child_item.IsOk(): if self.GetPyData(child_item)['layer'].IsRequestable() \ and not self.IsSelected(child_item): items_to_sel.append(child_item) elif not self.GetPyData(child_item)['layer'].IsRequestable(): list_to_check.append(child_item) child_item, cookie = self.GetNextChild(item, cookie) cur_item = event.GetItem() if not self.GetPyData(cur_item)['layer'].IsRequestable(): event.Veto() if not self.HasFlag(wx.TR_MULTIPLE): return _emitSelected(self.GetPyData(cur_item)['layer']) items_to_chck = [] items_to_sel = [] chck_item = cur_item while True: _selectRequestableChildren( chck_item, items_to_chck, items_to_sel) if items_to_chck: chck_item = items_to_chck.pop() else: break while items_to_sel: self.SelectItem(items_to_sel.pop(), unselect_others=False) else: _emitSelected(self.GetPyData(cur_item)['layer']) def GetItemCount(self): """Required for listmix.ListCtrlAutoWidthMixin """ return 0 def GetCountPerPage(self): """Required for listmix.ListCtrlAutoWidthMixin """ return 0 def SelectLayers(self, l_st_list): """Select layers/styles in LayersList :param l_st_list: [{style : 'style_name', layer : 'layer_name'}, ...] :return: items from l_st_list which were not found """ def checknext(item, l_st_list, items_to_sel): def compare(item, l_name, st_name): it_l_name = self.GetPyData(item)['layer'].GetLayerData('name') it_st = self.GetPyData(item)['style'] it_type = self.GetPyData(item)['type'] if it_l_name == l_name and ((not it_st and not st_name) or ( it_st and it_st['name'] == st_name and it_type == 'style')): return True return False for i, l_st in enumerate(l_st_list): l_name = l_st['layer'] st_name = l_st['style'] if compare(item, l_name, st_name): items_to_sel[i] = [item, l_st] break if len(items_to_sel) == len(l_st_list): item = self.GetNext(item) if not item.IsOk(): return checknext(item, l_st_list, items_to_sel) self.UnselectAll() l_st_list = deepcopy(l_st_list) root_item = self.GetRootItem() items_to_sel = [None] * len(l_st_list) checknext(root_item, l_st_list, items_to_sel) # items are selected according to position in l_st_list # to be added to Layers order list in right order for i in items_to_sel: if not i: continue item, l_st = i un_o = True if self.HasFlag(wx.TR_MULTIPLE): un_o = False self.SelectItem(item, unselect_others=un_o) l_st_list.remove(l_st) return l_st_list
class WSPanel(wx.Panel): def __init__(self, parent, web_service, **kwargs): """Show data from capabilities file. Signal: capParsed - this signal is emitted when capabilities file is downloaded (after ConnectToServer method was called) :param parent: parent widget :param web_service: web service to be panel generated for """ wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY) self.parent = parent self.ws = web_service self.capParsed = Signal('WSPanel.capParsed') # stores widgets, which represents parameters/flags of d.wms self.params = {} self.flags = {} self.o_layer_name = '' # stores err output from r.in.wms during getting capabilities self.cmd_err_str = '' # stores selected layer from layer list self.sel_layers = [] # downloaded and parsed data from server successfully? self.is_connected = False # common part of command for r.in.wms -c and d.wms self.ws_cmdl = None # provides information about driver parameters self.drv_info = WMSDriversInfo() self.drv_props = self.drv_info.GetDrvProperties(self.ws) self.ws_drvs = { 'WMS_1.1.1': { 'cmd': [ 'wms_version=1.1.1', 'driver=WMS_GRASS'], 'cap_parser': lambda temp_file: WMSCapabilities( temp_file, '1.1.1'), }, 'WMS_1.3.0': { 'cmd': [ 'wms_version=1.3.0', 'driver=WMS_GRASS'], 'cap_parser': lambda temp_file: WMSCapabilities( temp_file, '1.3.0'), }, 'WMTS': { 'cmd': ['driver=WMTS_GRASS'], 'cap_parser': WMTSCapabilities, }, 'OnEarth': { 'cmd': ['driver=OnEarth_GRASS'], 'cap_parser': OnEarthCapabilities, }} self.cmdStdErr = GStderr(self) self.cmd_thread = CmdThread(self) self.cap_file = grass.tempfile() reqDataBox = wx.StaticBox( parent=self, label=_(" Requested data settings ")) self._nb_sizer = wx.StaticBoxSizer(reqDataBox, wx.VERTICAL) self.notebook = GNotebook(parent=self, style=FN.FNB_FANCY_TABS | FN.FNB_NO_X_BUTTON) self._requestPage() self._advancedSettsPage() self._layout() self.layerSelected = self.list.layerSelected self.Bind(EVT_CMD_DONE, self.OnCapDownloadDone) self.Bind(EVT_CMD_OUTPUT, self.OnCmdOutput) def __del__(self): self.cmd_thread.abort(abortall=True) grass.try_remove(self.cap_file) def _layout(self): self._nb_sizer.Add(item=self.notebook, proportion=1, flag=wx.EXPAND) self.SetSizer(self._nb_sizer) def _requestPage(self): """Create request page""" self.req_page_panel = wx.Panel(parent=self, id=wx.ID_ANY) self.notebook.AddPage(page=self.req_page_panel, text=_('Request'), name='request') # list of layers self.layersBox = wx.StaticBox(parent=self.req_page_panel, id=wx.ID_ANY, label=_("List of layers ")) style = wx.TR_DEFAULT_STYLE | wx.TR_HAS_BUTTONS | wx.TR_FULL_ROW_HIGHLIGHT if self.drv_props['req_multiple_layers']: style = style | wx.TR_MULTIPLE if 'WMS' not in self.ws: style = style | wx.TR_HIDE_ROOT self.list = LayersList(parent=self.req_page_panel, web_service=self.ws, style=style) self.params['format'] = None self.params['srs'] = None if 'srs' not in self.drv_props['ignored_params']: projText = wx.StaticText( parent=self.req_page_panel, id=wx.ID_ANY, label=_("Source projection:")) self.params['srs'] = wx.Choice( parent=self.req_page_panel, id=wx.ID_ANY) self.list.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnListSelChanged) # layout self.req_page_sizer = wx.BoxSizer(wx.VERTICAL) layersSizer = wx.StaticBoxSizer(self.layersBox, wx.HORIZONTAL) layersSizer.Add( item=self.list, proportion=1, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border=5) self.req_page_sizer.Add( item=layersSizer, proportion=1, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border=5) self.source_sizer = wx.BoxSizer(wx.HORIZONTAL) if self.params['format'] is not None: self.source_sizer.Add( item=self.params['format'], flag=wx.LEFT | wx.RIGHT | wx.BOTTOM, border=5) if self.params['srs'] is not None: self.source_sizer.Add( item=projText, flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5) self.source_sizer.Add( item=self.params['srs'], flag=wx.ALIGN_CENTER_VERTICAL | wx.RIGHT | wx.TOP | wx.BOTTOM, border=5) self.req_page_sizer.Add( item=self.source_sizer, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border=5) self.req_page_panel.SetSizer(self.req_page_sizer) def enableButtons(self, enable=True): """Enable/disable up, down, buttons """ self.btnUp.Enable(enable) self.btnDown.Enable(enable) def _advancedSettsPage(self): """Create advanced settings page """ # TODO parse maxcol, maxrow, settings from d.wms module? # TODO OnEarth driver - add selection of time adv_setts_panel = wx.Panel(parent=self, id=wx.ID_ANY) self.notebook.AddPage(page=adv_setts_panel, text=_('Advanced request settings'), name='adv_req_setts') labels = {} self.l_odrder_list = None if 'WMS' in self.ws: labels['l_order'] = wx.StaticBox( parent=adv_setts_panel, id=wx.ID_ANY, label=_("Order of layers in raster")) self.l_odrder_list = wx.ListBox( adv_setts_panel, id=wx.ID_ANY, choices=[], style=wx.LB_SINGLE | wx.LB_NEEDED_SB) self.btnUp = wx.Button( adv_setts_panel, id=wx.ID_ANY, label=_("Up")) self.btnDown = wx.Button( adv_setts_panel, id=wx.ID_ANY, label=_("Down")) self.btnUp.Bind(wx.EVT_BUTTON, self.OnUp) self.btnDown.Bind(wx.EVT_BUTTON, self.OnDown) labels['method'] = wx.StaticText(parent=adv_setts_panel, id=wx.ID_ANY, label=_("Reprojection method:")) self.reproj_methods = ['nearest', 'linear', 'cubic', 'cubicspline'] self.params['method'] = wx.Choice( parent=adv_setts_panel, id=wx.ID_ANY, choices=[ _('Nearest neighbor'), _('Linear interpolation'), _('Cubic interpolation'), _('Cubic spline interpolation')]) labels['maxcols'] = wx.StaticText( parent=adv_setts_panel, id=wx.ID_ANY, label=_("Maximum columns to request from server at time:")) self.params['maxcols'] = wx.SpinCtrl( parent=adv_setts_panel, id=wx.ID_ANY, size=(100, -1)) labels['maxrows'] = wx.StaticText( parent=adv_setts_panel, id=wx.ID_ANY, label=_("Maximum rows to request from server at time:")) self.params['maxrows'] = wx.SpinCtrl( parent=adv_setts_panel, id=wx.ID_ANY, size=(100, -1)) min = 100 max = 10000 self.params['maxcols'].SetRange(min, max) self.params['maxrows'].SetRange(min, max) val = 500 self.params['maxcols'].SetValue(val) self.params['maxrows'].SetValue(val) self.flags['o'] = self.params['bgcolor'] = None if not 'o' in self.drv_props['ignored_flags']: self.flags['o'] = wx.CheckBox( parent=adv_setts_panel, id=wx.ID_ANY, label=_("Do not request transparent data")) self.flags['o'].Bind(wx.EVT_CHECKBOX, self.OnTransparent) labels['bgcolor'] = wx.StaticText( parent=adv_setts_panel, id=wx.ID_ANY, label=_("Background color:")) self.params['bgcolor'] = csel.ColourSelect( parent=adv_setts_panel, id=wx.ID_ANY, colour=( 255, 255, 255), size=globalvar.DIALOG_COLOR_SIZE) self.params['bgcolor'].Enable(False) self.params['urlparams'] = None if self.params['urlparams'] not in self.drv_props['ignored_params']: labels['urlparams'] = wx.StaticText( parent=adv_setts_panel, id=wx.ID_ANY, label=_("Additional query parameters for server:")) self.params['urlparams'] = wx.TextCtrl( parent=adv_setts_panel, id=wx.ID_ANY) # layout border = wx.BoxSizer(wx.VERTICAL) if 'WMS' in self.ws: boxSizer = wx.StaticBoxSizer(labels['l_order'], wx.VERTICAL) gridSizer = wx.GridBagSizer(hgap=3, vgap=3) gridSizer.Add(self.l_odrder_list, pos=(0, 0), span=(4, 1), flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, border=0) gridSizer.Add(self.btnUp, pos=(0, 1), flag=wx.ALIGN_CENTER_VERTICAL, border=0) gridSizer.Add(self.btnDown, pos=(1, 1), flag=wx.ALIGN_CENTER_VERTICAL, border=0) gridSizer.AddGrowableCol(0) boxSizer.Add(gridSizer, flag=wx.EXPAND | wx.ALL, border=5) border.Add(item=boxSizer, flag=wx.LEFT | wx.RIGHT | wx.UP | wx.EXPAND, border=5) gridSizer = wx.GridBagSizer(hgap=3, vgap=3) row = 0 for k in ['method', 'maxcols', 'maxrows', 'o', 'bgcolor']: if k in self.params: param = self.params[k] elif k in self.flags: param = self.flags[k] if param is None: continue if k in labels or k == 'o': if k != 'o': label = labels[k] else: label = param gridSizer.Add(label, flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, pos=(row, 0)) if k != 'o': gridSizer.Add(item=param, flag=wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, pos=(row, 1)) row += 1 gridSizer.AddGrowableCol(0) border.Add(item=gridSizer, flag=wx.LEFT | wx.RIGHT | wx.TOP | wx.EXPAND, border=5) if self.params['urlparams']: gridSizer = wx.GridBagSizer(hgap=3, vgap=3) row = 0 gridSizer.Add(labels['urlparams'], flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, pos=(row, 0)) gridSizer.Add(item=self.params['urlparams'], flag=wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, pos=(row, 1)) gridSizer.AddGrowableCol(1) border.Add(item=gridSizer, flag=wx.LEFT | wx.RIGHT | wx.TOP | wx.EXPAND, border=5) adv_setts_panel.SetSizer(border) def OnUp(self, event): """Move selected layer up """ if self.l_odrder_list.GetSelections(): pos = self.l_odrder_list.GetSelection() if pos: self.sel_layers.insert(pos - 1, self.sel_layers.pop(pos)) if pos > 0: self._updateLayerOrderList(selected=(pos - 1)) else: self._updateLayerOrderList(selected=0) def OnDown(self, event): """Move selected to down """ if self.l_odrder_list.GetSelections(): pos = self.l_odrder_list.GetSelection() if pos != len(self.sel_layers) - 1: self.sel_layers.insert(pos + 1, self.sel_layers.pop(pos)) if pos < len(self.sel_layers) - 1: self._updateLayerOrderList(selected=(pos + 1)) else: self._updateLayerOrderList(selected=len(self.sel_layers) - 1) def _updateLayerOrderList(self, selected=None): """Update order in list. """ def getlayercaption(layer): if l['title']: cap = (l['title']) else: cap = (l['name']) if l['style']: if l['style']['title']: cap += ' / ' + l['style']['title'] else: cap += ' / ' + l['style']['name'] return cap layer_capts = [getlayercaption(l) for l in self.sel_layers] self.l_odrder_list.Set(layer_capts) if self.l_odrder_list.IsEmpty(): self.enableButtons(False) else: self.enableButtons(True) if selected is not None: self.l_odrder_list.SetSelection(selected) self.l_odrder_list.EnsureVisible(selected) def OnTransparent(self, event): checked = event.IsChecked() if checked: self.params['bgcolor'].Enable(True) else: self.params['bgcolor'].Enable(False) def ConnectToServer(self, url, username, password): """Download and parse data from capabilities file. :param url: server url :type url: str :param username: username for connection :type username: str :param password: password for connection :type password: str """ self._prepareForNewConn(url, username, password) cap_cmd = [ 'r.in.wms', '-c', ('capfile_output=%s' % self.cap_file), '--overwrite'] + self.ws_cmdl self.currentPid = self.cmd_thread.GetId() self.cmd_thread.RunCmd(cap_cmd, stderr=self.cmdStdErr) def OnCmdOutput(self, event): """Manage cmd output. """ if Debug.GetLevel() != 0: Debug.msg(1, event.text) elif event.type != 'message' and event.type != 'warning': self.cmd_err_str += event.text + os.linesep def _prepareForNewConn(self, url, username, password): """Prepare panel for new connection """ self.is_connected = False self.sel_layers = [] self.formats_list = [] self.projs_list = [] self.conn = { 'url': url, 'password': password, 'username': username } conn_cmd = [] for k, v in self.conn.iteritems(): if v: conn_cmd.append("%s=%s" % (k, v)) self.ws_cmdl = self.ws_drvs[self.ws]['cmd'] + conn_cmd def OnCapDownloadDone(self, event): """Process donwloaded capabilities file and emits capParsed signal (see class constructor). """ if event.pid != self.currentPid: return if event.returncode != 0: if self.cmd_err_str: self.cmd_err_str = _( "Unable to download %s capabilities file\nfrom <%s>:\n" % (self.ws.replace('_', ' '), self.conn['url'])) + self.cmd_err_str self._postCapParsedEvt(error_msg=self.cmd_err_str) self.cmd_err_str = '' return self._parseCapFile(self.cap_file) def _parseCapFile(self, cap_file): """Parse capabilities data and emits capParsed signal (see class constructor). """ try: self.cap = self.ws_drvs[self.ws]['cap_parser'](cap_file) except (IOError, ParseError) as error: error_msg = _( "%s web service was not found in fetched capabilities file from <%s>:\n%s\n" % (self.ws, self.conn['url'], str(error))) if Debug.GetLevel() != 0: Debug.msg(1, error_msg) self._postCapParsedEvt(None) else: self._postCapParsedEvt(error_msg=error_msg) return self.is_connected = True # WMS standard has formats defined for all layers if 'WMS' in self.ws: self.formats_list = sorted(self._getFormats()) self._updateFormatRadioBox(self.formats_list) self._setDefaultFormatVal() self.list.LoadData(self.cap) self.OnListSelChanged(event=None) self._postCapParsedEvt(None) def ParseCapFile(self, url, username, password, cap_file=None,): """Parse capabilities data and emits capParsed signal (see class constructor). """ self._prepareForNewConn(url, username, password) if cap_file is None or not url: self._postCapParsedEvt(None) return shutil.copyfile(cap_file, self.cap_file) self._parseCapFile(self.cap_file) def UpdateWidgetsByCmd(self, cmd): """Update panel widgets accordnig to passed cmd tuple :param cmd: cmd in tuple """ dcmd = cmd[1] layers = [] if 'layers' in dcmd: layers = dcmd['layers'] styles = [] if 'styles' in dcmd: styles = dcmd['styles'] if 'WMS' in self.ws: layers = layers.split(',') styles = styles.split(',') else: layers = [layers] styles = [styles] if len(layers) != len(styles): styles = [''] * len(layers) l_st_list = [] for i in range(len(layers)): l_st_list.append({'style': styles[i], 'layer': layers[i]}) # WMS standard - first layer in params is most bottom... # therefore layers order need to be reversed l_st_list = [l for l in reversed(l_st_list)] self.list.SelectLayers(l_st_list) params = {} if 'format' in dcmd: params['format'] = dcmd['format'] if 'srs' in dcmd: params['srs'] = 'EPSG:' + dcmd['srs'] if 'method' in dcmd: params['method'] = dcmd['method'] for p, v in params.iteritems(): if self.params[p]: self.params[p].SetStringSelection(v) for p, conv_f in [ ('urlparams', None), ('maxcols', int), ('maxrows', int)]: if p in dcmd: v = dcmd[p] if conv_f: v = conv_f(v) self.params[p].SetValue(v) if 'flags' in dcmd and \ 'o' in dcmd['flags']: self.flags['o'].SetValue(1) self.params['bgcolor'].Enable(True) if 'bgcolor' in dcmd and \ self.params['bgcolor']: bgcolor = dcmd['bgcolor'].strip().lower() if len(bgcolor) == 8 and \ '0x' == bgcolor[:2]: colour = '#' + bgcolor[2:] self.params['bgcolor'].SetColour(colour) def IsConnected(self): """Was successful in downloading and parsing capabilities data? """ return self.is_connected def _postCapParsedEvt(self, error_msg): """Helper function """ self.capParsed.emit(error_msg=error_msg) def CreateCmd(self): """Create d.wms cmd from values of panels widgets :return: cmd list :return: None if required widgets do not have selected/filled values. """ # check required widgets if not self._checkImportValues(): return None # create d.wms command lcmd = self.ws_cmdl lcmd = ['d.wms'] + lcmd layers = "layers=" styles = 'styles=' first = True # WMS standard - first layer in params is most bottom... # therefore layers order need to be reversed for layer in reversed(self.sel_layers): if not first: layers += ',' styles += ',' first = False layers += layer['name'] if layer['style'] is not None: styles += layer['style']['name'] lcmd.append(layers) lcmd.append(styles) if 'format' not in self.drv_props['ignored_params']: i_format = self.params['format'].GetSelection() lcmd.append("format=%s" % self.formats_list[i_format]) if 'srs' not in self.drv_props['ignored_params']: i_srs = self.params['srs'].GetSelection() epsg_num = int(self.projs_list[i_srs].split(':')[-1]) lcmd.append("srs=%s" % epsg_num) for k in ['maxcols', 'maxrows', 'urlparams']: lcmd.append(k + '=' + str(self.params[k].GetValue())) i_method = self.params['method'].GetSelection() lcmd.append('method=' + self.reproj_methods[i_method]) if not 'o' in self.drv_props['ignored_flags'] and \ self.flags['o'].IsChecked(): lcmd.append('-o') c = self.params['bgcolor'].GetColour() hex_color = wx.Colour( c[0], c[1], c[2]).GetAsString( wx.C2S_HTML_SYNTAX) lcmd.append("bgcolor=" + '0x' + hex_color[1:]) lcmd.append("map=" + self.o_layer_name) return lcmd def OnListSelChanged(self, event): """Update widgets according to selected layer in list. """ curr_sel_ls = self.list.GetSelectedLayers() # update self.sel_layers (selected layer list) if 'WMS' in self.ws: for sel_l in self.sel_layers[:]: if sel_l not in curr_sel_ls: self.sel_layers.remove(sel_l) for l in curr_sel_ls: if l not in self.sel_layers: self.sel_layers.append(l) self._updateLayerOrderList() else: self.sel_layers = curr_sel_ls # update projection self.projs_list = [] projs_list = [] intersect_proj = [] first = True for l in curr_sel_ls: layer_projs = l['cap_intf_l'].GetLayerData('srs') if first: projs_list = layer_projs first = False continue projs_list = set(projs_list).intersection(layer_projs) if 'srs' not in self.drv_props['ignored_params']: for proj in projs_list: proj_code = Srs(proj.strip()).getcode() proj_spl = proj_code.split(':') if proj_spl[0].strip().lower() in self.drv_info.GetSrs(): try: int(proj_spl[1]) self.projs_list.append(proj_code) except ValueError as IndexError: continue cur_sel = self.params['srs'].GetStringSelection() self.projs_list = sorted(self.projs_list) self.params['srs'].SetItems(self.projs_list) if cur_sel: self.params['srs'].SetStringSelection(cur_sel) else: try: i = self.projs_list.index('EPSG:4326') self.params['srs'].SetSelection(i) except ValueError: if len(self.projs_list) > 0: self.params['srs'].SetSelection(0) # update format if 'WMS' not in self.ws and \ 'format' not in self.drv_props['ignored_params']: self.formats_list = [] cur_sel = None if self.params['format'] is not None: cur_sel = self.params['format'].GetStringSelection() if len(curr_sel_ls) > 0: self.formats_list = sorted( self._getFormats( curr_sel_ls[0]['cap_intf_l'])) self._updateFormatRadioBox(self.formats_list) if cur_sel: self.params['format'].SetStringSelection(cur_sel) else: self._setDefaultFormatVal() self.Layout() def _setDefaultFormatVal(self): """Set default format value. """ try: i = self.formats_list.index('png') self.params['format'].SetSelection(i) except ValueError: pass def _updateFormatRadioBox(self, formats_list): """Helper function """ if self.params['format'] is not None: self.req_page_sizer.Detach(self.params['format']) self.params['format'].Destroy() if len(self.formats_list) > 0: self.params['format'] = wx.RadioBox( parent=self.req_page_panel, id=wx.ID_ANY, label=_("Source image format"), pos=wx.DefaultPosition, choices=formats_list, majorDimension=4, style=wx.RA_SPECIFY_COLS) self.source_sizer.Insert(item=self.params['format'], before=2, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM, border=5) def _getFormats(self, layer=None): """Get formats WMS has formats defined generally for whole cap. In WMTS and NASA OnEarh formats are defined for layer. """ formats_label = [] if layer is None: formats_list = self.cap.GetFormats() else: formats_list = layer.GetLayerData('format') for frmt in formats_list: frmt = frmt.strip() label = self.drv_info.GetFormatLabel(frmt) if label: formats_label.append(label) return formats_label def _checkImportValues(self,): """Check if required widgets are selected/filled """ warning_str = "" show_war = False if not self.list or not self.list.GetSelectedLayers(): warning_str += _("Select layer in layer list.\n") show_war = True if self.params['format'] is not None and \ self.params['format'].GetSelection() == -1: warning_str += _("Select source image format.\n") show_war = True if self.params['srs'] is not None and \ self.params['srs'].GetSelection() == -1: warning_str += _("Select source projection.\n") show_war = True if not self.o_layer_name: warning_str += _("Choose output layer name.\n") show_war = True if show_war: GMessage(parent=self.parent, message=warning_str) return False return True def SetOutputLayerName(self, name): """Set name of layer to be added to layer tree """ self.o_layer_name = name def GetOutputLayerName(self): return self.o_layer_name def GetCapFile(self): """Get path to file where capabilities are saved """ return self.cap_file def GetWebService(self): """Get web service """ return self.ws
class Animation: """Class represents animation as a sequence of states (views). It enables to record, replay the sequence and finally generate all image files. Recording and replaying is based on timer events. There is no frame interpolation like in the Tcl/Tk based Nviz. """ def __init__(self, mapWindow, timer): """Animation constructor Signals: animationFinished - emitted when animation finished - attribute 'mode' animationUpdateIndex - emitted during animation to update gui - attributes 'index' and 'mode' :param mapWindow: glWindow where rendering takes place :param timer: timer for recording and replaying """ self.animationFinished = Signal('Animation.animationFinished') self.animationUpdateIndex = Signal('Animation.animationUpdateIndex') self.animationList = [] # view states self.timer = timer self.mapWindow = mapWindow self.actions = {'record': self.Record, 'play': self.Play} self.formats = ['tif', 'ppm'] # currently supported formats self.mode = 'record' # current mode (record, play, save) self.paused = False # recording/replaying paused self.currentFrame = 0 # index of current frame self.fps = 24 # user settings # Frames per second self.stopSaving = False # stop during saving images self.animationSaved = False # current animation saved or not def Start(self): """Start recording/playing""" self.timer.Start(self.GetInterval()) def Pause(self): """Pause recording/playing""" self.timer.Stop() def Stop(self): """Stop recording/playing""" self.timer.Stop() self.PostFinishedEvent() def Update(self): """Record/play next view state (on timer event)""" self.actions[self.mode]() def Record(self): """Record new view state""" self.animationList.append( {'view': copy.deepcopy(self.mapWindow.view), 'iview': copy.deepcopy(self.mapWindow.iview)}) self.currentFrame += 1 self.PostUpdateIndexEvent(index=self.currentFrame) self.animationSaved = False def Play(self): """Render next frame""" if not self.animationList: self.Stop() return try: self.IterAnimation() except IndexError: # no more frames self.Stop() def IterAnimation(self): params = self.animationList[self.currentFrame] self.UpdateView(params) self.currentFrame += 1 self.PostUpdateIndexEvent(index=self.currentFrame) def UpdateView(self, params): """Update view data in map window and render""" toolWin = self.mapWindow.GetToolWin() toolWin.UpdateState(view=params['view'], iview=params['iview']) self.mapWindow.UpdateView() self.mapWindow.render['quick'] = True self.mapWindow.Refresh(False) def IsRunning(self): """Test if timer is running""" return self.timer.IsRunning() def SetMode(self, mode): """Start animation mode :param mode: animation mode (record, play, save) """ self.mode = mode def GetMode(self): """Get animation mode (record, play, save)""" return self.mode def IsPaused(self): """Test if animation is paused""" return self.paused def SetPause(self, pause): self.paused = pause def Exists(self): """Returns if an animation has been recorded""" return bool(self.animationList) def GetFrameCount(self): """Return number of recorded frames""" return len(self.animationList) def Clear(self): """Clear all records""" self.animationList = [] self.currentFrame = 0 def GoToFrame(self, index): """Render frame of given index""" if index >= len(self.animationList): return self.currentFrame = index params = self.animationList[self.currentFrame] self.UpdateView(params) def PostFinishedEvent(self): """Animation ends""" self.animationFinished.emit(mode=self.mode) def PostUpdateIndexEvent(self, index): """Frame index changed, update tool window""" self.animationUpdateIndex(index=index, mode=self.mode) def StopSaving(self): """Abort image files generation""" self.stopSaving = True def IsSaved(self): """"Test if animation has been saved (to images)""" return self.animationSaved def SaveAnimationFile(self, path, prefix, format): """Generate image files :param path: path to direcory :param prefix: file prefix :param format: index of image file format """ w, h = self.mapWindow.GetClientSizeTuple() toolWin = self.mapWindow.GetToolWin() formatter = ':04.0f' n = len(self.animationList) if n < 10: formatter = ':01.0f' elif n < 100: formatter = ':02.0f' elif n < 1000: formatter = ':03.0f' self.currentFrame = 0 self.mode = 'save' for params in self.animationList: if not self.stopSaving: self.UpdateView(params) number = ( '{frame' + formatter + '}').format( frame=self.currentFrame) filename = "{prefix}_{number}.{ext}".format( prefix=prefix, number=number, ext=self.formats[format]) filepath = os.path.join(path, filename) self.mapWindow.SaveToFile( FileName=filepath, FileType=self.formats[format], width=w, height=h) self.currentFrame += 1 wx.Yield() toolWin.UpdateFrameIndex( index=self.currentFrame, goToFrame=False) else: self.stopSaving = False break self.animationSaved = True self.PostFinishedEvent() def SetFPS(self, fps): """Set Frames Per Second value :param fps: frames per second """ self.fps = fps def GetInterval(self): """Return timer interval in ms""" return 1000. / self.fps
class SingleSymbolPanel(wx.Panel): """Panel for displaying one symbol. Changes background when selected. Assumes that parent will catch events emitted on mouse click. Used in gui_core::dialog::SymbolDialog. """ def __init__(self, parent, symbolPath): """Panel constructor Signal symbolSelectionChanged - symbol selected - attribute 'name' (symbol name) - attribute 'doubleClick' (underlying cause) :param parent: parent (gui_core::dialog::SymbolDialog) :param symbolPath: absolute path to symbol """ self.symbolSelectionChanged = Signal('SingleSymbolPanel.symbolSelectionChanged') wx.Panel.__init__(self, parent, id=wx.ID_ANY, style=wx.BORDER_RAISED) self.SetName(os.path.splitext(os.path.basename(symbolPath))[0]) self.sBmp = wx.StaticBitmap(self, wx.ID_ANY, wx.Bitmap(symbolPath)) self.selected = False self.selectColor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT) self.deselectColor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW) sizer = wx.BoxSizer() sizer.Add(item = self.sBmp, proportion=0, flag=wx.ALL | wx.ALIGN_CENTER, border = 5) self.SetBackgroundColour(self.deselectColor) self.SetMinSize(self.GetBestSize()) self.SetSizerAndFit(sizer) # binding to both (staticBitmap, Panel) necessary self.sBmp.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) self.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick) self.sBmp.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick) def OnLeftDown(self, event): """Panel selected, background changes""" self.selected = True self.SetBackgroundColour(self.selectColor) self.Refresh() event.Skip() self.symbolSelectionChanged.emit(name=self.GetName(), doubleClick=False) def OnDoubleClick(self, event): self.symbolSelectionChanged.emit(name=self.GetName(), doubleClick=True) def Deselect(self): """Panel deselected, background changes back to default""" self.selected = False self.SetBackgroundColour(self.deselectColor) self.Refresh() def Select(self): """Select panel, no event emitted""" self.selected = True self.SetBackgroundColour(self.selectColor) self.Refresh()
class BitmapRenderer: """Class which renders 2D and 3D images to files.""" def __init__(self, mapFilesPool, tempDir, imageWidth, imageHeight): self._mapFilesPool = mapFilesPool self._tempDir = tempDir self.imageWidth = imageWidth self.imageHeight = imageHeight self.renderingContinues = Signal("BitmapRenderer.renderingContinues") self._stopRendering = False self._isRendering = False def Render(self, cmdList, regions, regionFor3D, bgcolor, force, nprocs): """Renders all maps and stores files. :param cmdList: list of rendering commands to run :param regions: regions for 2D rendering assigned to commands :param regionFor3D: region for setting 3D view :param bgcolor: background color as a tuple of 3 values 0 to 255 :param force: if True reload all data, otherwise only missing data :param nprocs: number of procs to be used for rendering """ Debug.msg(3, "BitmapRenderer.Render") count = 0 # Variables for parallel rendering proc_count = 0 proc_list = [] queue_list = [] cmd_list = [] filteredCmdList = [] for cmd, region in zip(cmdList, regions): if cmd[0] == "m.nviz.image": region = None filename = GetFileFromCmd(self._tempDir, cmd, region) if (not force and os.path.exists(filename) and self._mapFilesPool.GetSize(HashCmd( cmd, region)) == (self.imageWidth, self.imageHeight)): # for reference counting self._mapFilesPool[HashCmd(cmd, region)] = filename continue filteredCmdList.append((cmd, region)) mapNum = len(filteredCmdList) stopped = False self._isRendering = True for cmd, region in filteredCmdList: count += 1 # Queue object for interprocess communication q = Queue() # The separate render process if cmd[0] == "m.nviz.image": p = Process( target=RenderProcess3D, args=( self.imageWidth, self.imageHeight, self._tempDir, cmd, regionFor3D, bgcolor, q, ), ) else: p = Process( target=RenderProcess2D, args=( self.imageWidth, self.imageHeight, self._tempDir, cmd, region, bgcolor, q, ), ) p.start() queue_list.append(q) proc_list.append(p) cmd_list.append((cmd, region)) proc_count += 1 # Wait for all running processes and read/store the created images if proc_count == nprocs or count == mapNum: for i in range(len(cmd_list)): proc_list[i].join() filename = queue_list[i].get() self._mapFilesPool[HashCmd(cmd_list[i][0], cmd_list[i][1])] = filename self._mapFilesPool.SetSize( HashCmd(cmd_list[i][0], cmd_list[i][1]), (self.imageWidth, self.imageHeight), ) proc_count = 0 proc_list = [] queue_list = [] cmd_list = [] self.renderingContinues.emit(current=count, text=_("Rendering map layers")) if self._stopRendering: self._stopRendering = False stopped = True break self._isRendering = False return not stopped def RequestStopRendering(self): """Requests to stop rendering.""" if self._isRendering: self._stopRendering = True
class BitmapComposer: """Class which handles the composition of image files with g.pnmcomp.""" def __init__(self, tempDir, mapFilesPool, bitmapPool, imageWidth, imageHeight): self._mapFilesPool = mapFilesPool self._bitmapPool = bitmapPool self._tempDir = tempDir self.imageWidth = imageWidth self.imageHeight = imageHeight self.compositionContinues = Signal("BitmapComposer.composingContinues") self._stopComposing = False self._isComposing = False def Compose(self, cmdLists, regions, opacityList, bgcolor, force, nprocs): """Performs the composition of ppm/pgm files. :param cmdLists: lists of rendering commands lists to compose :param regions: regions for 2D rendering assigned to commands :param opacityList: list of lists of opacity values :param bgcolor: background color as a tuple of 3 values 0 to 255 :param force: if True reload all data, otherwise only missing data :param nprocs: number of procs to be used for rendering """ Debug.msg(3, "BitmapComposer.Compose") count = 0 # Variables for parallel rendering proc_count = 0 proc_list = [] queue_list = [] cmd_lists = [] filteredCmdLists = [] for cmdList, region in zip(cmdLists, regions): if (not force and HashCmds(cmdList, region) in self._bitmapPool and self._bitmapPool[HashCmds(cmdList, region)].GetSize() == (self.imageWidth, self.imageHeight)): # TODO: find a better way than to assign the same to increase # the reference self._bitmapPool[HashCmds(cmdList, region)] = self._bitmapPool[HashCmds( cmdList, region)] continue filteredCmdLists.append((cmdList, region)) num = len(filteredCmdLists) self._isComposing = True for cmdList, region in filteredCmdLists: count += 1 # Queue object for interprocess communication q = Queue() # The separate render process p = Process( target=CompositeProcess, args=( self.imageWidth, self.imageHeight, self._tempDir, cmdList, region, opacityList, bgcolor, q, ), ) p.start() queue_list.append(q) proc_list.append(p) cmd_lists.append((cmdList, region)) proc_count += 1 # Wait for all running processes and read/store the created images if proc_count == nprocs or count == num: for i in range(len(cmd_lists)): proc_list[i].join() filename = queue_list[i].get() if filename is None: self._bitmapPool[HashCmds( cmd_lists[i][0], cmd_lists[i][1])] = createNoDataBitmap( self.imageWidth, self.imageHeight, text="Failed to render") else: self._bitmapPool[HashCmds( cmd_lists[i][0], cmd_lists[i][1])] = BitmapFromImage( wx.Image(filename)) os.remove(filename) proc_count = 0 proc_list = [] queue_list = [] cmd_lists = [] self.compositionContinues.emit(current=count, text=_("Overlaying map layers")) if self._stopComposing: self._stopComposing = False break self._isComposing = False def RequestStopComposing(self): """Requests to stop the composition.""" if self._isComposing: self._stopComposing = True
class QueryDialog(wx.Dialog): def __init__(self, parent, data = None): wx.Dialog.__init__(self, parent, id = wx.ID_ANY, title = _("Query results"), size = (420, 400), style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) # send query output to console self.redirectOutput = Signal('QueryDialog.redirectOutput') self.data = data self.panel = wx.Panel(self, id = wx.ID_ANY) self.mainSizer = wx.BoxSizer(wx.VERTICAL) helpText = wx.StaticText(self.panel, wx.ID_ANY, label=_("Right click to copy selected values to clipboard.")) helpText.SetForegroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_GRAYTEXT)) self.mainSizer.Add(item=helpText, proportion=0, flag=wx.ALL, border=5) self._colNames = [_("Feature"), _("Value")] self._model = QueryTreeBuilder(self.data, column=self._colNames[1]) self.tree = TreeListView(model=self._model, parent=self.panel, columns=self._colNames, style=wx.TR_DEFAULT_STYLE | wx.TR_FULL_ROW_HIGHLIGHT | wx.TR_MULTIPLE) self.tree.SetColumnWidth(0, 220) self.tree.SetColumnWidth(1, 1000) self.tree.ExpandAll(self._model.root) self.tree.contextMenu.connect(self.ShowContextMenu) self.mainSizer.Add(item = self.tree, proportion = 1, flag = wx.EXPAND | wx.ALL, border = 5) close = wx.Button(self.panel, id = wx.ID_CLOSE) close.Bind(wx.EVT_BUTTON, lambda event: self.Close()) copy = wx.Button(self.panel, id = wx.ID_ANY, label = _("Copy all to clipboard")) copy.Bind(wx.EVT_BUTTON, self.Copy) self.Bind(wx.EVT_CLOSE, self.OnClose) self.redirect = wx.CheckBox(self.panel, label=_("Redirect to console")) self.redirect.SetValue(False) self.redirect.Bind(wx.EVT_CHECKBOX, lambda evt: self._onRedirect(evt.IsChecked())) hbox = wx.BoxSizer(wx.HORIZONTAL) hbox.Add(item=self.redirect, proportion=0, flag=wx.EXPAND | wx.RIGHT, border=5) hbox.AddStretchSpacer(1) hbox.Add(item=copy, proportion=0, flag=wx.EXPAND | wx.RIGHT, border=5) hbox.Add(item=close, proportion=0, flag=wx.EXPAND | wx.ALL, border=0) self.mainSizer.Add(item=hbox, proportion=0, flag=wx.EXPAND | wx.ALL, border=5) self.panel.SetSizer(self.mainSizer) self.mainSizer.Fit(self.panel) # for Windows self.SendSizeEvent() def SetData(self, data): state = self.tree.GetExpansionState() self.data = data self._model = QueryTreeBuilder(self.data, column=self._colNames[1]) self.tree.SetModel(self._model) self.tree.SetExpansionState(state) if self.redirect.IsChecked(): self.redirectOutput.emit(output=self._textToRedirect()) def Copy(self, event): text = printResults(self._model, self._colNames[1]) self._copyText(text) def ShowContextMenu(self, node): """Show context menu. Menu for copying distinguishes single and multiple selection. """ nodes = self.tree.GetSelected() if not nodes: return menu = wx.Menu() texts = [] if len(nodes) > 1: values = [] for node in nodes: values.append((node.label, node.data[self._colNames[1]] if node.data else '')) col1 = '\n'.join([val[1] for val in values if val[1]]) col2 = '\n'.join([val[0] for val in values if val[0]]) table = '\n'.join([val[0] + ': ' + val[1] for val in values]) texts.append((_("Copy from '%s' column") % self._colNames[1], col1)) texts.append((_("Copy from '%s' column") % self._colNames[0], col2)) texts.append((_("Copy selected lines"), table)) else: label1 = nodes[0].label texts.append((_("Copy '%s'" % self._cutLabel(label1)), label1)) if nodes[0].data and nodes[0].data[self._colNames[1]]: label2 = nodes[0].data[self._colNames[1]] texts.insert(0, (_("Copy '%s'" % self._cutLabel(label2)), label2)) texts.append((_("Copy line"), label1 + ': ' + label2)) ids = [] for text in texts: id = wx.NewId() ids.append(id) self.Bind(wx.EVT_MENU, lambda evt, t=text[1], id=id: self._copyText(t), id=id) menu.Append(id, text[0]) # show the popup menu self.PopupMenu(menu) menu.Destroy() for id in ids: self.Unbind(wx.EVT_MENU, id=id) def _onRedirect(self, redirect): """Emits instructions to redirect query results. :param redirect: True to start redirecting, False to stop """ if redirect: self.redirectOutput.emit(output=_("Query results:"), style='cmd') self.redirectOutput.emit(output=self._textToRedirect()) else: self.redirectOutput.emit(output=_(" "), style='cmd') def _textToRedirect(self): text = printResults(self._model, self._colNames[1]) text += '\n' + "-"* 50 + '\n' return text def _cutLabel(self, label): limit = 15 if len(label) > limit: return label[:limit] + '...' return label def _copyText(self, text): """Helper function for copying""" if wx.TheClipboard.Open(): do = wx.TextDataObject() do.SetText(text) wx.TheClipboard.SetData(do) wx.TheClipboard.Close() def OnClose(self, event): if self.redirect.IsChecked(): self._onRedirect(False) self.Destroy() event.Skip()
class Statistics: """Statistis conected to one class (category). It is Python counterpart of similar C structure. But it adds some attributes or features used in wxIClass. It is not interface to C structure (it copies values). """ def __init__(self): self.category = -1 self.name = "" self.rasterName = "" self.color = "0:0:0" self.nbands = 0 self.ncells = 0 self.nstd = 1.5 self.bands = [] self.ready = False self.statisticsSet = Signal("Statistics.statisticsSet") def SetReady(self, ready=True): self.ready = ready def IsReady(self): return self.ready def SetBaseStatistics(self, cat, name, color): """Sets basic (non-statistical) values. .. todo:: Later self.name is changed but self.rasterName is not. self.rasterName should not be set by user. It can remains the same. But it should be done more explicitly. Currently it looks like unintentional feature or bug. """ self.category = cat self.name = name self.color = color rasterPath = grass.tempfile(create=False) name = name.replace(' ', '_') self.rasterName = name + '_' + os.path.basename(rasterPath) def SetFromcStatistics(self, cStatistics): """Sets all statistical values. Copies all statistic values from \a cStattistics. :param cStatistics: pointer to C statistics structure """ cat = c_int() set_stats = {} I_iclass_statistics_get_cat(cStatistics, byref(cat)) if self.category != cat.value: set_stats["category"] = cat.value name = c_char_p() I_iclass_statistics_get_name(cStatistics, byref(name)) if self.name != name.value: set_stats["name"] = name.value color = c_char_p() I_iclass_statistics_get_color(cStatistics, byref(color)) if self.color != color.value: set_stats["color"] = color.value nbands = c_int() I_iclass_statistics_get_nbands(cStatistics, byref(nbands)) if self.nbands != nbands.value: set_stats["nbands"] = nbands.value ncells = c_int() I_iclass_statistics_get_ncells(cStatistics, byref(ncells)) if self.ncells != ncells.value: set_stats["ncells"] = ncells.value nstd = c_float() I_iclass_statistics_get_nstd(cStatistics, byref(nstd)) if self.nstd != nstd.value: set_stats["nstd"] = nstd.value self.SetStatistics(set_stats) self.SetBandStatistics(cStatistics) def SetBandStatistics(self, cStatistics): """Sets all band statistics. :param cStatistics: pointer to C statistics structure """ self.bands = [] for i in range(self.nbands): band = BandStatistics() band.SetFromcStatistics(cStatistics, index=i) self.bands.append(band) def SetStatistics(self, stats): for st, val in stats.iteritems(): setattr(self, st, val) self.statisticsSet.emit(stats=stats)
class SimpleLayerManager(wx.Panel): """Simple layer manager class provides similar functionality to Layertree, but it's just list, not tree.""" def __init__( self, parent, layerList, lmgrStyle=SIMPLE_LMGR_RASTER | SIMPLE_LMGR_VECTOR | SIMPLE_LMGR_TB_LEFT, toolbarCls=None, modal=False, ): wx.Panel.__init__(self, parent=parent, name="SimpleLayerManager") self._style = lmgrStyle self._layerList = layerList self._checkList = wx.CheckListBox(self, style=wx.LB_EXTENDED) if not toolbarCls: toolbarCls = SimpleLmgrToolbar self._toolbar = toolbarCls(self, lmgrStyle=self._style) self._auimgr = wx.aui.AuiManager(self) self._modal = modal # d.* dialogs are recreated each time, attempt to hide it resulted # in completely mysterious memory corruption and crash when opening # any dialog with stock labels (wx.ID_OK and so on) # needed in order not to change selection when moving layers self._blockSelectionChanged = False self._checkList.Bind(wx.EVT_LISTBOX, lambda evt: self._selectionChanged()) self._checkList.Bind(wx.EVT_LISTBOX_DCLICK, self.OnLayerChangeProperties) self._checkList.Bind(wx.EVT_CHECKLISTBOX, self.OnLayerChecked) self._checkList.Bind(wx.EVT_CONTEXT_MENU, self.OnContextMenu) # signal emitted when somethin in layer list changes self.opacityChanged = Signal("SimpleLayerManager.opacityChanged") self.cmdChanged = Signal("SimpleLayerManager.cmdChanged") self.layerAdded = Signal("SimpleLayerManager.layerAdded") self.layerRemoved = Signal("SimpleLayerManager.layerRemoved") self.layerActivated = Signal("SimpleLayerManager.layerActivated") self.layerMovedUp = Signal("SimpleLayerManager.layerMovedUp") self.layerMovedDown = Signal("SimpleLayerManager.layerMovedDown") # emitted by any change (e.g. for rerendering) self.anyChange = Signal("SimpleLayerManager.layerChange") self._layout() self.SetMinSize((200, -1)) self._update() def _layout(self): self._auimgr.AddPane( self._checkList, wx.aui.AuiPaneInfo() .Name("checklist") .CenterPane() .CloseButton(False) .BestSize((self._checkList.GetBestSize())), ) paneInfo = ( wx.aui.AuiPaneInfo() .Name("toolbar") .Caption(_("Toolbar")) .ToolbarPane() .CloseButton(False) .Layer(1) .Gripper(False) .BestSize((self._toolbar.GetBestSize())) ) if self._style & SIMPLE_LMGR_TB_LEFT: paneInfo.Left() elif self._style & SIMPLE_LMGR_TB_RIGHT: paneInfo.Right() elif self._style & SIMPLE_LMGR_TB_TOP: paneInfo.Top() else: paneInfo.Bottom() self._auimgr.AddPane(self._toolbar, paneInfo) self._auimgr.Update() def _selectionChanged(self): """Selection was changed externally, updates selection info in layers.""" if self._blockSelectionChanged: return selected = self._checkList.GetSelections() for i, layer in enumerate(self._layerList): layer.Select(i in selected) def OnContextMenu(self, event): """Show context menu. So far offers only copying layer list to clipboard """ if len(self._layerList) < 1: event.Skip() return menu = wx.Menu() llist = [layer.name for layer in self._layerList] texts = [",".join(llist), ",".join(reversed(llist))] labels = [_("Copy map names to clipboard (top to bottom)"), _("Copy map names to clipboard (bottom to top)")] for label, text in zip(labels, texts): id = wx.NewId() self.Bind(wx.EVT_MENU, lambda evt, t=text, id=id: self._copyText(t), id=id) menu.Append(id, label) # show the popup menu self.PopupMenu(menu) menu.Destroy() event.Skip() def _copyText(self, text): """Helper function for copying TODO: move to utils? """ if wx.TheClipboard.Open(): do = wx.TextDataObject() do.SetText(text) wx.TheClipboard.SetData(do) wx.TheClipboard.Close() def OnLayerChecked(self, event): """Layer was (un)checked, update layer's info.""" checkedIdxs = self._checkList.GetChecked() for i, layer in enumerate(self._layerList): if i in checkedIdxs and not layer.IsActive(): layer.Activate() self.layerActivated.emit(index=i, layer=layer) elif i not in checkedIdxs and layer.IsActive(): layer.Activate(False) self.layerActivated.emit(index=i, layer=layer) self.anyChange.emit() event.Skip() def OnAddRaster(self, event): """Opens d.rast dialog and adds layer. Dummy layer is added first.""" cmd = ["d.rast"] layer = self.AddRaster(name="", cmd=cmd, hidden=True, dialog=None) GUI(parent=self, giface=None, modal=self._modal).ParseCommand(cmd=cmd, completed=(self.GetOptData, layer, "")) event.Skip() def OnAddVector(self, event): """Opens d.vect dialog and adds layer. Dummy layer is added first.""" cmd = ["d.vect"] layer = self.AddVector(name="", cmd=cmd, hidden=True, dialog=None) GUI(parent=self, giface=None, modal=self._modal).ParseCommand(cmd=cmd, completed=(self.GetOptData, layer, "")) event.Skip() def OnAddRast3d(self, event): """Opens d.rast3d dialog and adds layer. Dummy layer is added first.""" cmd = ["d.rast3d"] layer = self.AddRast3d(name="", cmd=cmd, hidden=True, dialog=None) GUI(parent=self, giface=None, modal=self._modal).ParseCommand(cmd=cmd, completed=(self.GetOptData, layer, "")) event.Skip() def OnAddRGB(self, event): """Opens d.rgb dialog and adds layer. Dummy layer is added first.""" cmd = ["d.rgb"] layer = self.AddRGB(name="", cmd=cmd, hidden=True, dialog=None) GUI(parent=self, giface=None, modal=self._modal).ParseCommand(cmd=cmd, completed=(self.GetOptData, layer, "")) event.Skip() def OnRemove(self, event): """Removes selected layers from list.""" layers = self._layerList.GetSelectedLayers(activeOnly=False) for layer in layers: self.layerRemoved.emit(index=self._layerList.GetLayerIndex(layer), layer=layer) self._layerList.RemoveLayer(layer) self._update() self.anyChange.emit() event.Skip() def OnLayerUp(self, event): """Moves selected layers one step up. Note: not completely correct for multiple layers.""" layers = self._layerList.GetSelectedLayers() self._blockSelectionChanged = True for layer in layers: idx = self._layerList.GetLayerIndex(layer) if idx > 0: self.layerMovedUp.emit(index=idx, layer=layer) self._layerList.MoveLayerUp(layer) self._update() self._blockSelectionChanged = False self.anyChange.emit() event.Skip() def OnLayerDown(self, event): """Moves selected layers one step down. Note: not completely correct for multiple layers.""" layers = self._layerList.GetSelectedLayers() self._blockSelectionChanged = True for layer in layers: idx = self._layerList.GetLayerIndex(layer) if idx < len(self._layerList) - 1: self.layerMovedDown.emit(index=self._layerList.GetLayerIndex(layer), layer=layer) self._layerList.MoveLayerDown(layer) self._update() self._blockSelectionChanged = False self.anyChange.emit() event.Skip() def OnLayerChangeProperties(self, event): """Opens module dialog to edit layer command.""" layers = self._layerList.GetSelectedLayers() if not layers or len(layers) > 1: return self._layerChangeProperties(layers[0]) event.Skip() def _layerChangeProperties(self, layer): """Opens new module dialog or recycles it.""" GUI(parent=self, giface=None, modal=self._modal).ParseCommand( cmd=layer.cmd, completed=(self.GetOptData, layer, "") ) def OnLayerChangeOpacity(self, event): """Opacity of a layer is changing.""" layers = self._layerList.GetSelectedLayers() if not layers or len(layers) > 1: return layer = layers[0] dlg = SetOpacityDialog(self, opacity=layer.opacity, title=_("Set opacity of <%s>") % layer.name) dlg.applyOpacity.connect(lambda value: self._setLayerOpacity(layer, value)) dlg.CentreOnParent() if dlg.ShowModal() == wx.ID_OK: self._setLayerOpacity(layer, dlg.GetOpacity()) dlg.Destroy() event.Skip() def _setLayerOpacity(self, layer, value): """Sets layer's opacity.'""" layer.opacity = value self._update() self.opacityChanged.emit(index=self._layerList.GetLayerIndex(layer), layer=layer) self.anyChange.emit() def _update(self): """Updates checklistbox according to layerList structure.""" items = [] active = [] selected = [] # remove hidden (temporary) layers first for layer in reversed(self._layerList): if layer.hidden: self._layerList.RemoveLayer(layer) for layer in self._layerList: if layer.opacity < 1: items.append("{name} (opacity {opacity}%)".format(name=layer.name, opacity=int(layer.opacity * 100))) else: items.append(layer.name) active.append(layer.IsActive()) selected.append(layer.IsSelected()) self._checkList.SetItems(items) for i, check in enumerate(active): self._checkList.Check(i, check) for i, layer in enumerate(self._layerList): if selected[i]: self._checkList.Select(i) else: self._checkList.Deselect(i) def GetOptData(self, dcmd, layer, params, propwin): """Handler for module dialogs.""" if dcmd: layer.cmd = dcmd layer.selected = True mapName, found = GetLayerNameFromCmd(dcmd) if found: try: if layer.hidden: layer.hidden = False signal = self.layerAdded else: signal = self.cmdChanged layer.name = mapName signal.emit(index=self._layerList.GetLayerIndex(layer), layer=layer) except ValueError as e: self._layerList.RemoveLayer(layer) GError(parent=self, message=str(e), showTraceback=False) self._update() self.anyChange.emit() def AddRaster(self, name, cmd, hidden, dialog): """Ads new raster layer.""" layer = self._layerList.AddNewLayer(name=name, mapType="raster", active=True, cmd=cmd, hidden=hidden) return layer def AddRast3d(self, name, cmd, hidden, dialog): """Ads new raster3d layer.""" layer = self._layerList.AddNewLayer(name=name, mapType="raster_3d", active=True, cmd=cmd, hidden=hidden) return layer def AddVector(self, name, cmd, hidden, dialog): """Ads new vector layer.""" layer = self._layerList.AddNewLayer(name=name, mapType="vector", active=True, cmd=cmd, hidden=hidden) return layer def AddRGB(self, name, cmd, hidden, dialog): """Ads new vector layer.""" layer = self._layerList.AddNewLayer(name=name, mapType="rgb", active=True, cmd=cmd, hidden=hidden) return layer def GetLayerInfo(self, layer, key): """Just for compatibility, should be removed in the future""" value = getattr(layer, key) # hack to return empty list, required in OnCancel in forms # not sure why it should be empty if key == "cmd" and len(value) == 1: return [] return value def Delete(self, layer): """Just for compatibility, should be removed in the future""" self._layerList.RemoveLayer(layer)
class QueryDialog(wx.Dialog): def __init__(self, parent, data=None): wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title=_("Query results"), size=(420, 400), style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) # send query output to console self.redirectOutput = Signal('QueryDialog.redirectOutput') self.data = data self.panel = wx.Panel(self, id=wx.ID_ANY) self.mainSizer = wx.BoxSizer(wx.VERTICAL) helpText = StaticText( self.panel, wx.ID_ANY, label=_("Right click to copy selected values to clipboard.")) helpText.SetForegroundColour( wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT)) self.mainSizer.Add(helpText, proportion=0, flag=wx.ALL, border=5) self._colNames = [_("Feature"), _("Value")] self._model = QueryTreeBuilder(self.data, column=self._colNames[1]) self.tree = TreeListView(model=self._model, parent=self.panel, columns=self._colNames, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT | wx.TR_FULL_ROW_HIGHLIGHT | wx.TR_MULTIPLE) self.tree.SetColumnWidth(0, 220) self.tree.SetColumnWidth(1, 1000) self.tree.ExpandAll(self._model.root) self.tree.RefreshItems() self.tree.contextMenu.connect(self.ShowContextMenu) self.mainSizer.Add(self.tree, proportion=1, flag=wx.EXPAND | wx.ALL, border=5) close = Button(self.panel, id=wx.ID_CLOSE) close.Bind(wx.EVT_BUTTON, lambda event: self.Close()) copy = Button(self.panel, id=wx.ID_ANY, label=_("Copy all to clipboard")) copy.Bind(wx.EVT_BUTTON, self.Copy) self.Bind(wx.EVT_CLOSE, self.OnClose) self.redirect = wx.CheckBox(self.panel, label=_("Redirect to console")) self.redirect.SetValue(False) self.redirect.Bind(wx.EVT_CHECKBOX, lambda evt: self._onRedirect(evt.IsChecked())) hbox = wx.BoxSizer(wx.HORIZONTAL) hbox.Add(self.redirect, proportion=0, flag=wx.EXPAND | wx.RIGHT, border=5) hbox.AddStretchSpacer(1) hbox.Add(copy, proportion=0, flag=wx.EXPAND | wx.RIGHT, border=5) hbox.Add(close, proportion=0, flag=wx.EXPAND | wx.ALL, border=0) self.mainSizer.Add(hbox, proportion=0, flag=wx.EXPAND | wx.ALL, border=5) self.panel.SetSizer(self.mainSizer) self.mainSizer.Fit(self.panel) # for Windows self.SendSizeEvent() def SetData(self, data): state = self.tree.GetExpansionState() self.data = data self._model = QueryTreeBuilder(self.data, column=self._colNames[1]) self.tree.SetModel(self._model) self.tree.SetExpansionState(state) if self.redirect.IsChecked(): self.redirectOutput.emit(output=self._textToRedirect()) def Copy(self, event): text = printResults(self._model, self._colNames[1]) self._copyText(text) def ShowContextMenu(self, node): """Show context menu. Menu for copying distinguishes single and multiple selection. """ nodes = self.tree.GetSelected() if not nodes: return menu = Menu() texts = [] if len(nodes) > 1: values = [] for node in nodes: values.append( (node.label, node.data[self._colNames[1]] if node.data else '')) col1 = '\n'.join([val[1] for val in values if val[1]]) col2 = '\n'.join([val[0] for val in values if val[0]]) table = '\n'.join([val[0] + ': ' + val[1] for val in values]) texts.append( (_("Copy from '%s' column") % self._colNames[1], col1)) texts.append( (_("Copy from '%s' column") % self._colNames[0], col2)) texts.append((_("Copy selected lines"), table)) else: label1 = nodes[0].label texts.append((_("Copy '%s'" % self._cutLabel(label1)), label1)) if nodes[0].data and nodes[0].data[self._colNames[1]]: label2 = nodes[0].data[self._colNames[1]] texts.insert(0, (_("Copy '%s'" % self._cutLabel(label2)), label2)) texts.append((_("Copy line"), label1 + ': ' + label2)) ids = [] for text in texts: id = NewId() ids.append(id) self.Bind(wx.EVT_MENU, lambda evt, t=text[1], id=id: self._copyText(t), id=id) menu.Append(id, text[0]) # show the popup menu self.PopupMenu(menu) menu.Destroy() for id in ids: self.Unbind(wx.EVT_MENU, id=id) def _onRedirect(self, redirect): """Emits instructions to redirect query results. :param redirect: True to start redirecting, False to stop """ if redirect: self.redirectOutput.emit(output=_("Query results:"), style='cmd') self.redirectOutput.emit(output=self._textToRedirect()) else: self.redirectOutput.emit(output=_(" "), style='cmd') def _textToRedirect(self): text = printResults(self._model, self._colNames[1]) text += '\n' + "-" * 50 + '\n' return text def _cutLabel(self, label): limit = 15 if len(label) > limit: return label[:limit] + '...' return label def _copyText(self, text): """Helper function for copying""" if wx.TheClipboard.Open(): do = wx.TextDataObject() do.SetText(text) wx.TheClipboard.SetData(do) wx.TheClipboard.Close() def OnClose(self, event): if self.redirect.IsChecked(): self._onRedirect(False) self.Destroy() event.Skip()
class SbProgress(SbItem): """General progress bar to show progress. Underlaying widget is wx.Gauge. """ def __init__(self, mapframe, statusbar, sbManager, position=0): self.progressShown = Signal("SbProgress.progressShown") self.progressHidden = Signal("SbProgress.progressHidden") SbItem.__init__(self, mapframe, statusbar, position) self.name = "progress" self.sbManager = sbManager # on-render gauge self.widget = wx.Gauge(parent=self.statusbar, id=wx.ID_ANY, range=0, style=wx.GA_HORIZONTAL) self.Hide() def GetRange(self): """Returns progress range.""" return self.widget.GetRange() def SetRange(self, range): """Sets progress range.""" if range > 0: if self.GetRange() != range: self.widget.SetRange(range) self.Show() else: self.Hide() def Show(self): if not self.IsShown(): self.progressShown.emit() self.widget.Show() def Hide(self): if self.IsShown(): self.progressHidden.emit() self.widget.Hide() def IsShown(self): """Is progress bar shown """ return self.widget.IsShown() def SetValue(self, value): """Sets value of progressbar.""" if value > self.GetRange(): self.Hide() return self.widget.SetValue(value) if value == self.GetRange(): self.Hide() def GetWidget(self): """Returns underlaying winget. :return: widget or None if doesn't exist """ return self.widget def Update(self): pass
class ScatterPlotWidget(wx.Panel, ManageBusyCursorMixin): def __init__(self, parent, scatt_id, scatt_mgr, transpose, id=wx.ID_ANY): # TODO should not be transpose and scatt_id but x, y wx.Panel.__init__(self, parent, id) # bacause of aui (if floatable it can not take cursor from parent) ManageBusyCursorMixin.__init__(self, window=self) self.parent = parent self.full_extend = None self.mode = None self._createWidgets() self._doLayout() self.scatt_id = scatt_id self.scatt_mgr = scatt_mgr self.cidpress = None self.cidrelease = None self.rend_dt = {} self.transpose = transpose self.inverse = False self.SetSize((200, 100)) self.Layout() self.base_scale = 1.2 self.Bind(wx.EVT_CLOSE, lambda event: self.CleanUp()) self.plotClosed = Signal("ScatterPlotWidget.plotClosed") self.cursorMove = Signal("ScatterPlotWidget.cursorMove") self.contex_menu = ScatterPlotContextMenu(plot=self) self.ciddscroll = None self.canvas.mpl_connect('motion_notify_event', self.Motion) self.canvas.mpl_connect('button_press_event', self.OnPress) self.canvas.mpl_connect('button_release_event', self.OnRelease) self.canvas.mpl_connect('draw_event', self.DrawCallback) self.canvas.mpl_connect('figure_leave_event', self.OnCanvasLeave) def DrawCallback(self, event): self.polygon_drawer.DrawCallback(event) self.axes.draw_artist(self.zoom_rect) def _createWidgets(self): # Create the mpl Figure and FigCanvas objects. # 5x4 inches, 100 dots-per-inch # self.dpi = 100 self.fig = Figure((1.0, 1.0), dpi=self.dpi) self.fig.autolayout = True self.canvas = FigCanvas(self, -1, self.fig) self.axes = self.fig.add_axes([0.0, 0.0, 1, 1]) pol = Polygon(list(zip([0], [0])), animated=True) self.axes.add_patch(pol) self.polygon_drawer = PolygonDrawer(self.axes, pol=pol, empty_pol=True) self.zoom_wheel_coords = None self.zoom_rect_coords = None self.zoom_rect = Polygon(list(zip([0], [0])), facecolor='none') self.zoom_rect.set_visible(False) self.axes.add_patch(self.zoom_rect) def ZoomToExtend(self): if self.full_extend: self.axes.axis(self.full_extend) self.canvas.draw() def SetMode(self, mode): self._deactivateMode() if mode == 'zoom': self.ciddscroll = self.canvas.mpl_connect('scroll_event', self.ZoomWheel) self.mode = 'zoom' elif mode == 'zoom_extend': self.mode = 'zoom_extend' elif mode == 'pan': self.mode = 'pan' elif mode: self.polygon_drawer.SetMode(mode) def SetSelectionPolygonMode(self, activate): self.polygon_drawer.SetSelectionPolygonMode(activate) def _deactivateMode(self): self.mode = None self.polygon_drawer.SetMode(None) if self.ciddscroll: self.canvas.mpl_disconnect(self.ciddscroll) self.zoom_rect.set_visible(False) self._stopCategoryEdit() def GetCoords(self): coords = self.polygon_drawer.GetCoords() if coords is None: return if self.transpose: for c in coords: tmp = c[0] c[0] = c[1] c[1] = tmp return coords def SetEmpty(self): return self.polygon_drawer.SetEmpty() def OnRelease(self, event): if not self.mode == "zoom": return self.zoom_rect.set_visible(False) self.ZoomRectangle(event) self.canvas.draw() def OnPress(self, event): 'on button press we will see if the mouse is over us and store some data' if not event.inaxes: return if self.mode == "zoom_extend": self.ZoomToExtend() if event.xdata and event.ydata: self.zoom_wheel_coords = {'x': event.xdata, 'y': event.ydata} self.zoom_rect_coords = {'x': event.xdata, 'y': event.ydata} else: self.zoom_wheel_coords = None self.zoom_rect_coords = None def _stopCategoryEdit(self): 'disconnect all the stored connection ids' if self.cidpress: self.canvas.mpl_disconnect(self.cidpress) if self.cidrelease: self.canvas.mpl_disconnect(self.cidrelease) # self.canvas.mpl_disconnect(self.cidmotion) def _doLayout(self): self.main_sizer = wx.BoxSizer(wx.VERTICAL) self.main_sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW) self.SetSizer(self.main_sizer) self.main_sizer.Fit(self) def Plot(self, cats_order, scatts, ellipses, styles): """Redraws the figure """ callafter_list = [] if self.full_extend: cx = self.axes.get_xlim() cy = self.axes.get_ylim() c = cx + cy else: c = None q = Queue() _rendDtMemmapsToFiles(self.rend_dt) p = Process(target=MergeImg, args=(cats_order, scatts, styles, self.rend_dt, q)) p.start() merged_img, self.full_extend, self.rend_dt = q.get() p.join() _rendDtFilesToMemmaps(self.rend_dt) merged_img = np.memmap(filename=merged_img['dt'], shape=merged_img['sh']) #merged_img, self.full_extend = MergeImg(cats_order, scatts, styles, None) self.axes.clear() self.axes.axis('equal') if self.transpose: merged_img = np.transpose(merged_img, (1, 0, 2)) img = imshow(self.axes, merged_img, extent=[int(ceil(x)) for x in self.full_extend], origin='lower', interpolation='nearest', aspect="equal") callafter_list.append([self.axes.draw_artist, [img]]) callafter_list.append([grass.try_remove, [merged_img.filename]]) for cat_id in cats_order: if cat_id == 0: continue if cat_id not in ellipses: continue e = ellipses[cat_id] if not e: continue colors = styles[cat_id]['color'].split(":") if self.transpose: e['theta'] = 360 - e['theta'] + 90 if e['theta'] >= 360: e['theta'] = abs(360 - e['theta']) e['pos'] = [e['pos'][1], e['pos'][0]] ellip = Ellipse(xy=e['pos'], width=e['width'], height=e['height'], angle=e['theta'], edgecolor="w", linewidth=1.5, facecolor='None') self.axes.add_artist(ellip) callafter_list.append([self.axes.draw_artist, [ellip]]) color = [ int(v) / 255.0 for v in styles[cat_id]['color'].split(":")[:3] ] ellip = Ellipse(xy=e['pos'], width=e['width'], height=e['height'], angle=e['theta'], edgecolor=color, linewidth=1, facecolor='None') self.axes.add_artist(ellip) callafter_list.append([self.axes.draw_artist, [ellip]]) center = Line2D( [e['pos'][0]], [e['pos'][1]], marker='x', markeredgecolor='w', # markerfacecolor=color, markersize=2) self.axes.add_artist(center) callafter_list.append([self.axes.draw_artist, [center]]) callafter_list.append([self.fig.canvas.blit, []]) if c: self.axes.axis(c) wx.CallAfter(lambda: self.CallAfter(callafter_list)) def CallAfter(self, funcs_list): while funcs_list: fcn, args = funcs_list.pop(0) fcn(*args) self.canvas.draw() def CleanUp(self): self.plotClosed.emit(scatt_id=self.scatt_id) self.Destroy() def ZoomWheel(self, event): # get the current x and y limits if not event.inaxes: return # tcaswell # http://stackoverflow.com/questions/11551049/matplotlib-plot-zooming-with-scroll-wheel cur_xlim = self.axes.get_xlim() cur_ylim = self.axes.get_ylim() xdata = event.xdata ydata = event.ydata if event.button == 'up': scale_factor = 1 / self.base_scale elif event.button == 'down': scale_factor = self.base_scale else: scale_factor = 1 extend = (xdata - (xdata - cur_xlim[0]) * scale_factor, xdata + (cur_xlim[1] - xdata) * scale_factor, ydata - (ydata - cur_ylim[0]) * scale_factor, ydata + (cur_ylim[1] - ydata) * scale_factor) self.axes.axis(extend) self.canvas.draw() def ZoomRectangle(self, event): # get the current x and y limits if not self.mode == "zoom": return if event.inaxes is None: return if event.button != 1: return cur_xlim = self.axes.get_xlim() cur_ylim = self.axes.get_ylim() x1, y1 = event.xdata, event.ydata x2 = deepcopy(self.zoom_rect_coords['x']) y2 = deepcopy(self.zoom_rect_coords['y']) if x1 == x2 or y1 == y2: return if x1 > x2: tmp = x1 x1 = x2 x2 = tmp if y1 > y2: tmp = y1 y1 = y2 y2 = tmp self.axes.axis((x1, x2, y1, y2)) # self.axes.set_xlim(x1, x2)#, auto = True) # self.axes.set_ylim(y1, y2)#, auto = True) self.canvas.draw() def Motion(self, event): self.PanMotion(event) self.ZoomRectMotion(event) if event.inaxes is None: return self.cursorMove.emit(x=event.xdata, y=event.ydata, scatt_id=self.scatt_id) def OnCanvasLeave(self, event): self.cursorMove.emit(x=None, y=None, scatt_id=self.scatt_id) def PanMotion(self, event): 'on mouse movement' if not self.mode == "pan": return if event.inaxes is None: return if event.button != 1: return cur_xlim = self.axes.get_xlim() cur_ylim = self.axes.get_ylim() x, y = event.xdata, event.ydata mx = (x - self.zoom_wheel_coords['x']) * 0.6 my = (y - self.zoom_wheel_coords['y']) * 0.6 extend = (cur_xlim[0] - mx, cur_xlim[1] - mx, cur_ylim[0] - my, cur_ylim[1] - my) self.zoom_wheel_coords['x'] = x self.zoom_wheel_coords['y'] = y self.axes.axis(extend) # self.canvas.copy_from_bbox(self.axes.bbox) # self.canvas.restore_region(self.background) self.canvas.draw() def ZoomRectMotion(self, event): if not self.mode == "zoom": return if event.inaxes is None: return if event.button != 1: return x1, y1 = event.xdata, event.ydata self.zoom_rect.set_visible(True) x2 = self.zoom_rect_coords['x'] y2 = self.zoom_rect_coords['y'] self.zoom_rect.xy = ((x1, y1), (x1, y2), (x2, y2), (x2, y1), (x1, y1)) # self.axes.draw_artist(self.zoom_rect) self.canvas.draw()
class MapWindowProperties(object): def __init__(self): self._resolution = None self.resolutionChanged = Signal('MapWindowProperties.resolutionChanged') self._autoRender = None self.autoRenderChanged = Signal('MapWindowProperties.autoRenderChanged') self._showRegion = None self.showRegionChanged = Signal('MapWindowProperties.showRegionChanged') self._alignExtent = None self.alignExtentChanged = Signal('MapWindowProperties.alignExtentChanged') def setValuesFromUserSettings(self): """Convenient function to get values from user settings into this object.""" self._resolution = UserSettings.Get(group='display', key='compResolution', subkey='enabled') self._autoRender = UserSettings.Get(group='display', key='autoRendering', subkey='enabled') self._showRegion = False # in statusbar.py was not from settings self._alignExtent = UserSettings.Get(group='display', key='alignExtent', subkey='enabled') @property def resolution(self): return self._resolution @resolution.setter def resolution(self, value): if value != self._resolution: self._resolution = value self.resolutionChanged.emit(value=value) @property def autoRender(self): return self._autoRender @autoRender.setter def autoRender(self, value): if value != self._autoRender: self._autoRender = value self.autoRenderChanged.emit(value=value) @property def showRegion(self): return self._showRegion @showRegion.setter def showRegion(self, value): if value != self._showRegion: self._showRegion = value self.showRegionChanged.emit(value=value) @property def alignExtent(self): return self._alignExtent @alignExtent.setter def alignExtent(self, value): if value != self._alignExtent: self._alignExtent = value self.alignExtentChanged.emit(value=value)
class RenderWMSMgr(wx.EvtHandler): """Fetch and prepare WMS data for rendering. """ def __init__(self, layer, mapfile, maskfile): if not haveGdal: sys.stderr.write(_("Unable to load GDAL Python bindings.\n"\ "WMS layers can not be displayed without the bindings.\n")) self.layer = layer wx.EvtHandler.__init__(self) # thread for d.wms commands self.thread = CmdThread(self) self.Bind(EVT_CMD_DONE, self.OnDataFetched) self.downloading = False self.renderedRegion = None self.updateMap = True self.fetched_data_cmd = None self.cmdStdErr = GStderr(self) self.mapfile = mapfile self.maskfile = maskfile self.tempMap = grass.tempfile() self.dstSize = {} self.Bind(EVT_CMD_OUTPUT, self.OnCmdOutput) self.dataFetched = Signal('RenderWMSMgr.dataFetched') self.updateProgress = Signal('RenderWMSMgr.updateProgress') def __del__(self): try_remove(self.tempMap) def Render(self, cmd, env): """If it is needed, download missing WMS data. .. todo:: lmgr deletes mapfile and maskfile when order of layers was changed (drag and drop) - if deleted, fetch data again """ if not haveGdal: return env = copy.copy(env) self.dstSize['cols'] = int(env["GRASS_RENDER_WIDTH"]) self.dstSize['rows'] = int(env["GRASS_RENDER_HEIGHT"]) region = self._getRegionDict(env) self._fitAspect(region, self.dstSize) self.updateMap = True fetchData = False zoomChanged = False if self.renderedRegion is None or \ cmd != self.fetched_data_cmd: fetchData = True else: for c in ['north', 'south', 'east', 'west']: if self.renderedRegion and \ region[c] != self.renderedRegion[c]: fetchData = True break for c in ['e-w resol', 'n-s resol']: if self.renderedRegion and \ region[c] != self.renderedRegion[c]: zoomChanged = True break if fetchData: self.fetched_data_cmd = None self.renderedRegion = region try_remove(self.mapfile) try_remove(self.tempMap) self.currentPid = self.thread.GetId() self.thread.abort() self.downloading = True self.fetching_cmd = cmd cmdList = utils.CmdTupleToList(cmd) if Debug.GetLevel() < 3: cmdList.append('--quiet') env["GRASS_RENDER_FILE"] = self.tempMap env["GRASS_REGION"] = self._createRegionStr(region) self.thread.RunCmd(cmdList, env=env, stderr=self.cmdStdErr) def OnCmdOutput(self, event): """Print cmd output according to debug level. """ if Debug.GetLevel() == 0: if event.type == 'error': sys.stderr.write(event.text) sys.stderr.flush() else: Debug.msg(1, event.text) def OnDataFetched(self, event): """Fetch data """ if event.pid != self.currentPid: return self.downloading = False if not self.updateMap: self.updateProgress.emit(layer=self.layer) self.renderedRegion = None self.fetched_data_cmd = None return self.mapMerger = GDALRasterMerger(targetFile = self.mapfile, region = self.renderedRegion, bandsNum = 3, gdalDriver = 'PNM', fillValue = 0) self.mapMerger.AddRasterBands(self.tempMap, {1 : 1, 2 : 2, 3 : 3}) del self.mapMerger self.maskMerger = GDALRasterMerger(targetFile = self.maskfile, region = self.renderedRegion, bandsNum = 1, gdalDriver = 'PNM', fillValue = 0) #{4 : 1} alpha channel (4) to first and only channel (1) in mask self.maskMerger.AddRasterBands(self.tempMap, {4 : 1}) del self.maskMerger self.fetched_data_cmd = self.fetching_cmd self.dataFetched.emit() def _getRegionDict(self, env): """Parse string from GRASS_REGION env variable into dict. """ region = {} parsedRegion = env["GRASS_REGION"].split(';') for r in parsedRegion: r = r.split(':') r[0] = r[0].strip() if len(r) < 2: continue try: if r[0] in ['cols', 'rows']: region[r[0]] = int(r[1]) else: region[r[0]] = float(r[1]) except ValueError: region[r[0]] = r[1] return region def _createRegionStr(self, region): """Create string for GRASS_REGION env variable from dict created by _getRegionDict. """ regionStr = '' for k, v in region.iteritems(): item = k + ': ' + str(v) if regionStr: regionStr += '; ' regionStr += item return regionStr def IsDownloading(self): """Is it downloading any data from WMS server? """ return self.downloading def _fitAspect(self, region, size): """Compute region parameters to have direction independent resolution. """ if region['n-s resol'] > region['e-w resol']: delta = region['n-s resol'] * size['cols'] / 2 center = (region['east'] - region['west'])/2 region['east'] = center + delta + region['west'] region['west'] = center - delta + region['west'] region['e-w resol'] = region['n-s resol'] else: delta = region['e-w resol'] * size['rows'] / 2 center = (region['north'] - region['south'])/2 region['north'] = center + delta + region['south'] region['south'] = center - delta + region['south'] region['n-s resol'] = region['e-w resol'] def Abort(self): """Abort process""" self.updateMap = False self.thread.abort(abortall = True)
class MapWindowBase(object): """Abstract map display window class Superclass for BufferedWindow class (2D display mode), and GLWindow (3D display mode). Subclasses have to define - _bindMouseEvents method which binds MouseEvent handlers - Pixel2Cell - Cell2Pixel (if it is possible) """ def __init__(self, parent, giface, Map): self.parent = parent self.Map = Map self._giface = giface # Emitted when someone registers as mouse event handler self.mouseHandlerRegistered = Signal('MapWindow.mouseHandlerRegistered') # Emitted when mouse event handler is unregistered self.mouseHandlerUnregistered = Signal('MapWindow.mouseHandlerUnregistered') # emitted after double click in pointer mode on legend, text, scalebar self.overlayActivated = Signal('MapWindow.overlayActivated') # emitted when overlay should be hidden self.overlayHidden = Signal('MapWindow.overlayHidden') # mouse attributes -- position on the screen, begin and end of # dragging, and type of drawing self.mouse = { 'begin': [0, 0], # screen coordinates 'end' : [0, 0], 'use' : "pointer", 'box' : "point" } # last east, north coordinates, changes on mouse motion self.lastEN = None # stores overridden cursor self._overriddenCursor = None # dictionary where event types are stored as keys and lists of # handlers for these types as values self.handlersContainer = { wx.EVT_LEFT_DOWN : [], wx.EVT_LEFT_UP : [], wx.EVT_LEFT_DCLICK : [], wx.EVT_MIDDLE_DOWN : [], wx.EVT_MIDDLE_UP : [], wx.EVT_MIDDLE_DCLICK : [], wx.EVT_RIGHT_DOWN : [], wx.EVT_RIGHT_UP : [], wx.EVT_RIGHT_DCLICK : [], wx.EVT_MOTION : [], wx.EVT_ENTER_WINDOW : [], wx.EVT_LEAVE_WINDOW : [], wx.EVT_MOUSEWHEEL : [], wx.EVT_MOUSE_EVENTS : [] } # available cursors self._cursors = { "default": wx.StockCursor(wx.CURSOR_ARROW), "cross": wx.StockCursor(wx.CURSOR_CROSS), "hand": wx.StockCursor(wx.CURSOR_HAND), "pencil": wx.StockCursor(wx.CURSOR_PENCIL), "sizenwse": wx.StockCursor(wx.CURSOR_SIZENWSE) } # default cursor for window is arrow (at least we rely on it here) # but we need to define attribute here # cannot call SetNamedCursor since it expects the instance # to be a wx window, so setting only the attribute self._cursor = 'default' wx.CallAfter(self.InitBinding) def __del__(self): self.UnregisterAllHandlers() def InitBinding(self): """Binds helper functions, which calls all handlers registered to events with the events """ for ev, handlers in self.handlersContainer.iteritems(): self.Bind(ev, self.EventTypeHandler(handlers)) def EventTypeHandler(self, evHandlers): return lambda event : self.HandlersCaller(event, evHandlers) def HandlersCaller(self, event, handlers): """Hepler function which calls all handlers registered for event """ for handler in handlers: try: handler(event) except: handlers.remove(handler) GError(parent=self, message=_("Error occurred during calling of handler: %s \n" "Handler was unregistered.") % handler.__name__) event.Skip() def RegisterMouseEventHandler(self, event, handler, cursor=None): """Binds event handler @depreciated This method is depreciated. Use Signals or drawing API instead. Signals do not cover all events but new Signals can be added when needed consider also adding generic signal. However, more interesing and useful is higher level API to create objects, graphics etc. Call event.Skip() in handler to allow default processing in MapWindow. If any error occures inside of handler, the handler is removed. Before handler is unregistered it is called with string value "unregistered" of event parameter. :: # your class methods def OnButton(self, event): # current map display's map window # expects LayerManager to be the parent self.mapwin = self.parent.GetLayerTree().GetMapDisplay().GetWindow() if self.mapwin.RegisterEventHandler(wx.EVT_LEFT_DOWN, self.OnMouseAction, 'cross'): self.parent.GetLayerTree().GetMapDisplay().Raise() else: # handle that you cannot get coordinates def OnMouseAction(self, event): # get real world coordinates of mouse click coor = self.mapwin.Pixel2Cell(event.GetPositionTuple()[:]) self.text.SetLabel('Coor: ' + str(coor)) self.mapwin.UnregisterMouseEventHandler(wx.EVT_LEFT_DOWN, self.OnMouseAction) event.Skip() Emits mouseHandlerRegistered signal before handler is registered. :param event: one of mouse events :param handler: function to handle event :param cursor: cursor which temporary overrides current cursor :return: True if successful :return: False if event cannot be bind """ self.mouseHandlerRegistered.emit() # inserts handler into list for containerEv, handlers in self.handlersContainer.iteritems(): if event == containerEv: handlers.append(handler) self.mouse['useBeforeGenericEvent'] = self.mouse['use'] self.mouse['use'] = 'genericEvent' if cursor: self._overriddenCursor = self.GetNamedCursor() self.SetNamedCursor(cursor) return True def UnregisterAllHandlers(self): """Unregisters all registered handlers @depreciated This method is depreciated. Use Signals or drawing API instead. Before each handler is unregistered it is called with string value "unregistered" of event parameter. """ for containerEv, handlers in self.handlersContainer.iteritems(): for handler in handlers: try: handler("unregistered") handlers.remove(handler) except: GError(parent = self, message = _("Error occurred during unregistration of handler: %s \n \ Handler was unregistered.") % handler.__name__) handlers.remove(handler) def UnregisterMouseEventHandler(self, event, handler): """Unbinds event handler for event @depreciated This method is depreciated. Use Signals or drawing API instead. Before handler is unregistered it is called with string value "unregistered" of event parameter. Emits mouseHandlerUnregistered signal after handler is unregistered. :param handler: handler to unbind :param event: event from which handler will be unbinded :return: True if successful :return: False if event cannot be unbind """ # removes handler from list for containerEv, handlers in self.handlersContainer.iteritems(): if event != containerEv: continue try: handler("unregistered") if handler in handlers: handlers.remove(handler) else: grass.warning(_("Handler: %s was not registered") \ % handler.__name__) except: GError(parent = self, message = _("Error occurred during unregistration of handler: %s \n \ Handler was unregistered") % handler.__name__) handlers.remove(handler) # restore mouse use (previous state) self.mouse['use'] = self.mouse['useBeforeGenericEvent'] # restore overridden cursor if self._overriddenCursor: self.SetNamedCursor(self._overriddenCursor) self.mouseHandlerUnregistered.emit() return True def Pixel2Cell(self, xyCoords): raise NotImplementedError() def Cell2Pixel(self, enCoords): raise NotImplementedError() def OnMotion(self, event): """Tracks mouse motion and update statusbar .. todo:: remove this method when lastEN is not used :func:`GetLastEN` """ try: self.lastEN = self.Pixel2Cell(event.GetPositionTuple()) except (ValueError): self.lastEN = None event.Skip() def GetLastEN(self): """Returns last coordinates of mouse cursor. @depreciated This method is depreciated. Use Signal with coordinates as parameters. :func:`OnMotion` """ return self.lastEN def SetNamedCursor(self, cursorName): """Sets cursor defined by name.""" cursor = self._cursors[cursorName] self.SetCursor(cursor) self._cursor = cursorName def GetNamedCursor(self): """Returns current cursor name.""" return self._cursor cursor = property(fget=GetNamedCursor, fset=SetNamedCursor) def SetModePointer(self): """Sets mouse mode to pointer.""" self.mouse['use'] = 'pointer' self.mouse['box'] = 'point' self.SetNamedCursor('default') def SetModePan(self): """Sets mouse mode to pan.""" self.mouse['use'] = "pan" self.mouse['box'] = "box" self.zoomtype = 0 self.SetNamedCursor('hand') def SetModeZoomIn(self): self._setModeZoom(zoomType=1) def SetModeZoomOut(self): self._setModeZoom(zoomType=-1) def _setModeZoom(self, zoomType): self.zoomtype = zoomType self.mouse['use'] = "zoom" self.mouse['box'] = "box" self.pen = wx.Pen(colour='Red', width=2, style=wx.SHORT_DASH) self.SetNamedCursor('cross') def SetModeDrawRegion(self): self.mouse['use'] = 'drawRegion' self.mouse['box'] = "box" self.pen = wx.Pen(colour='Red', width=2, style=wx.SHORT_DASH) self.SetNamedCursor('cross') def SetModeQuery(self): """Query mode on""" self.mouse['use'] = "query" self.mouse['box'] = "point" self.zoomtype = 0 self.SetNamedCursor('cross') def DisactivateWin(self): """Use when the class instance is hidden in MapFrame.""" raise NotImplementedError() def ActivateWin(self): """Used when the class instance is activated in MapFrame.""" raise NotImplementedError()
class GPromptSTC(GPrompt, wx.stc.StyledTextCtrl): """Styled wxGUI prompt with autocomplete and calltips""" def __init__(self, parent, menuModel, margin = False): GPrompt.__init__(self, parent = parent, menuModel = menuModel) wx.stc.StyledTextCtrl.__init__(self, self.panel, id = wx.ID_ANY) # # styles # self.SetWrapMode(True) self.SetUndoCollection(True) # # create command and map lists for autocompletion # self.AutoCompSetIgnoreCase(False) # # line margins # # TODO print number only from cmdlog self.SetMarginWidth(1, 0) self.SetMarginWidth(2, 0) if margin: self.SetMarginType(0, wx.stc.STC_MARGIN_NUMBER) self.SetMarginWidth(0, 30) else: self.SetMarginWidth(0, 0) # # miscellaneous # self.SetViewWhiteSpace(False) self.SetUseTabs(False) self.UsePopUp(True) self.SetSelBackground(True, "#FFFF00") self.SetUseHorizontalScrollBar(True) # # bindings # self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy) self.Bind(wx.EVT_CHAR, self.OnChar) self.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed) self.Bind(wx.stc.EVT_STC_AUTOCOMP_SELECTION, self.OnItemSelected) self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemChanged) if sys.platform != 'darwin': # unstable on Mac with wxPython 3 self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus) # signal which requests showing of a notification self.showNotification = Signal('GPromptSTC.showNotification') # signal to notify selected command self.commandSelected = Signal('GPromptSTC.commandSelected') def OnTextSelectionChanged(self, event): """Copy selected text to clipboard and skip event. The same function is in GStc class (goutput.py). """ wx.CallAfter(self.Copy) event.Skip() def OnItemChanged(self, event): """Change text in statusbar if the item selection in the auto-completion list is changed""" # list of commands if self.toComplete['entity'] == 'command': item = self.toComplete['cmd'].rpartition('.')[0] + '.' + self.autoCompList[event.GetIndex()] try: nodes = self._menuModel.SearchNodes(key='command', value=item) desc = '' if nodes: self.commandSelected.emit(command=item) desc = nodes[0].data['description'] except KeyError: desc = '' self.ShowStatusText(desc) # list of flags elif self.toComplete['entity'] == 'flags': desc = self.cmdDesc.get_flag(self.autoCompList[event.GetIndex()])['description'] self.ShowStatusText(desc) # list of parameters elif self.toComplete['entity'] == 'params': item = self.cmdDesc.get_param(self.autoCompList[event.GetIndex()]) desc = item['name'] + '=' + item['type'] if not item['required']: desc = '[' + desc + ']' desc += ': ' + item['description'] self.ShowStatusText(desc) # list of flags and commands elif self.toComplete['entity'] == 'params+flags': if self.autoCompList[event.GetIndex()][0] == '-': desc = self.cmdDesc.get_flag(self.autoCompList[event.GetIndex()].strip('-'))['description'] else: item = self.cmdDesc.get_param(self.autoCompList[event.GetIndex()]) desc = item['name'] + '=' + item['type'] if not item['required']: desc = '[' + desc + ']' desc += ': ' + item['description'] self.ShowStatusText(desc) else: self.ShowStatusText('') def OnItemSelected(self, event): """Item selected from the list""" lastWord = self.GetWordLeft() # to insert selection correctly if selected word partly matches written text match = difflib.SequenceMatcher(None, event.GetText(), lastWord) matchTuple = match.find_longest_match(0, len(event.GetText()), 0, len(lastWord)) compl = event.GetText()[matchTuple[2]:] text = self.GetTextLeft() + compl # add space or '=' at the end end = '=' for char in ('.','-','='): if text.split(' ')[-1].find(char) >= 0: end = ' ' compl += end text += end self.AddText(compl) pos = len(text) self.SetCurrentPos(pos) cmd = text.strip().split(' ')[0] if not self.cmdDesc or cmd != self.cmdDesc.get_name(): try: self.cmdDesc = gtask.parse_interface(cmd) except IOError: self.cmdDesc = None def OnKillFocus(self, event): """Hides autocomplete""" # hide autocomplete if self.AutoCompActive(): self.AutoCompCancel() event.Skip() def SetTextAndFocus(self, text): pos = len(text) self.commandSelected.emit(command=text) self.SetText(text) self.SetSelectionStart(pos) self.SetCurrentPos(pos) self.SetFocus() def UpdateCmdHistory(self, cmd): """Update command history :param cmd: command given as a string """ # add command to history self.cmdbuffer.append(cmd) # keep command history to a managable size if len(self.cmdbuffer) > 200: del self.cmdbuffer[0] self.cmdindex = len(self.cmdbuffer) def EntityToComplete(self): """Determines which part of command (flags, parameters) should be completed at current cursor position""" entry = self.GetTextLeft() toComplete = dict(cmd=None, entity=None) try: cmd = entry.split()[0].strip() except IndexError: return toComplete try: splitted = utils.split(str(entry)) except ValueError: # No closing quotation error return toComplete if len(splitted) > 0 and cmd in globalvar.grassCmd: toComplete['cmd'] = cmd if entry[-1] == ' ': words = entry.split(' ') if any(word.startswith('-') for word in words): toComplete['entity'] = 'params' else: toComplete['entity'] = 'params+flags' else: # get word left from current position word = self.GetWordLeft(withDelimiter = True) if word[0] == '=' and word[-1] == '@': toComplete['entity'] = 'mapsets' elif word[0] == '=': # get name of parameter paramName = self.GetWordLeft(withDelimiter = False, ignoredDelimiter = '=').strip('=') if paramName: try: param = self.cmdDesc.get_param(paramName) except (ValueError, AttributeError): return toComplete else: return toComplete if param['values']: toComplete['entity'] = 'param values' elif param['prompt'] == 'raster' and param['element'] == 'cell': toComplete['entity'] = 'raster map' elif param['prompt'] == 'vector' and param['element'] == 'vector': toComplete['entity'] = 'vector map' elif word[0] == '-': toComplete['entity'] = 'flags' elif word[0] == ' ': toComplete['entity'] = 'params' else: toComplete['entity'] = 'command' toComplete['cmd'] = cmd return toComplete def GetWordLeft(self, withDelimiter = False, ignoredDelimiter = None): """Get word left from current cursor position. The beginning of the word is given by space or chars: .,-= :param withDelimiter: returns the word with the initial delimeter :param ignoredDelimiter: finds the word ignoring certain delimeter """ textLeft = self.GetTextLeft() parts = list() if ignoredDelimiter is None: ignoredDelimiter = '' for char in set(' .,-=') - set(ignoredDelimiter): if not withDelimiter: delimiter = '' else: delimiter = char parts.append(delimiter + textLeft.rpartition(char)[2]) return min(parts, key=lambda x: len(x)) def ShowList(self): """Show sorted auto-completion list if it is not empty""" if len(self.autoCompList) > 0: self.autoCompList.sort() self.AutoCompShow(lenEntered = 0, itemList = ' '.join(self.autoCompList)) def OnKeyPressed(self, event): """Key pressed capture special treatment for tabulator to show help""" pos = self.GetCurrentPos() if event.GetKeyCode() == wx.WXK_TAB: # show GRASS command calltips (to hide press 'ESC') entry = self.GetTextLeft() try: cmd = entry.split()[0].strip() except IndexError: cmd = '' if cmd not in globalvar.grassCmd: return info = gtask.command_info(cmd) self.CallTipSetBackground("#f4f4d1") self.CallTipSetForeground("BLACK") self.CallTipShow(pos, info['usage'] + '\n\n' + info['description']) elif event.GetKeyCode() in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER) and \ not self.AutoCompActive(): # run command on line when <return> is pressed self._runCmd(self.GetCurLine()[0].strip()) elif event.GetKeyCode() in [wx.WXK_UP, wx.WXK_DOWN] and \ not self.AutoCompActive(): # Command history using up and down if len(self.cmdbuffer) < 1: return self.DocumentEnd() # move through command history list index values if event.GetKeyCode() == wx.WXK_UP: self.cmdindex = self.cmdindex - 1 if event.GetKeyCode() == wx.WXK_DOWN: self.cmdindex = self.cmdindex + 1 if self.cmdindex < 0: self.cmdindex = 0 if self.cmdindex > len(self.cmdbuffer) - 1: self.cmdindex = len(self.cmdbuffer) - 1 try: # without strip causes problem on windows txt = self.cmdbuffer[self.cmdindex].strip() except KeyError: txt = '' # clear current line and insert command history self.DelLineLeft() self.DelLineRight() pos = self.GetCurrentPos() self.InsertText(pos, txt) self.LineEnd() self.ShowStatusText('') else: event.Skip() def OnChar(self, event): """Key char capture for autocompletion, calltips, and command history .. todo:: event.ControlDown() for manual autocomplete """ # keycodes used: "." = 46, "=" = 61, "-" = 45 pos = self.GetCurrentPos() # complete command after pressing '.' if event.GetKeyCode() == 46: self.autoCompList = list() entry = self.GetTextLeft() self.InsertText(pos, '.') self.CharRight() self.toComplete = self.EntityToComplete() try: if self.toComplete['entity'] == 'command': for command in globalvar.grassCmd: try: if command.find(self.toComplete['cmd']) == 0: dotNumber = list(self.toComplete['cmd']).count('.') self.autoCompList.append(command.split('.',dotNumber)[-1]) except UnicodeDecodeError as e: # TODO: fix it sys.stderr.write(DecodeString(command) + ": " + unicode(e)) except (KeyError, TypeError): return self.ShowList() # complete flags after pressing '-' elif (event.GetKeyCode() == 45) \ or event.GetKeyCode() == wx.WXK_NUMPAD_SUBTRACT \ or event.GetKeyCode() == wx.WXK_SUBTRACT: self.autoCompList = list() entry = self.GetTextLeft() self.InsertText(pos, '-') self.CharRight() self.toComplete = self.EntityToComplete() if self.toComplete['entity'] == 'flags' and self.cmdDesc: if self.GetTextLeft()[-2:] == ' -': # complete e.g. --quite for flag in self.cmdDesc.get_options()['flags']: if len(flag['name']) == 1: self.autoCompList.append(flag['name']) else: for flag in self.cmdDesc.get_options()['flags']: if len(flag['name']) > 1: self.autoCompList.append(flag['name']) self.ShowList() # complete map or values after parameter elif event.GetKeyCode() == 61: self.autoCompList = list() self.InsertText(pos, '=') self.CharRight() self.toComplete = self.EntityToComplete() if self.toComplete['entity'] == 'raster map': self.autoCompList = self.mapList['raster'] elif self.toComplete['entity'] == 'vector map': self.autoCompList = self.mapList['vector'] elif self.toComplete['entity'] == 'param values': param = self.GetWordLeft(withDelimiter = False, ignoredDelimiter='=').strip(' =') self.autoCompList = self.cmdDesc.get_param(param)['values'] self.ShowList() # complete mapset ('@') elif event.GetKeyCode() == 64: self.autoCompList = list() self.InsertText(pos, '@') self.CharRight() self.toComplete = self.EntityToComplete() if self.toComplete['entity'] == 'mapsets': self.autoCompList = self.mapsetList self.ShowList() # complete after pressing CTRL + Space elif event.GetKeyCode() == wx.WXK_SPACE and event.ControlDown(): self.autoCompList = list() self.toComplete = self.EntityToComplete() #complete command if self.toComplete['entity'] == 'command': for command in globalvar.grassCmd: if command.find(self.toComplete['cmd']) == 0: dotNumber = list(self.toComplete['cmd']).count('.') self.autoCompList.append(command.split('.',dotNumber)[-1]) # complete flags in such situations (| is cursor): # r.colors -| ...w, q, l # r.colors -w| ...w, q, l elif self.toComplete['entity'] == 'flags' and self.cmdDesc: for flag in self.cmdDesc.get_options()['flags']: if len(flag['name']) == 1: self.autoCompList.append(flag['name']) # complete parameters in such situations (| is cursor): # r.colors -w | ...color, map, rast, rules # r.colors col| ...color elif self.toComplete['entity'] == 'params' and self.cmdDesc: for param in self.cmdDesc.get_options()['params']: if param['name'].find(self.GetWordLeft(withDelimiter=False)) == 0: self.autoCompList.append(param['name']) # complete flags or parameters in such situations (| is cursor): # r.colors | ...-w, -q, -l, color, map, rast, rules # r.colors color=grey | ...-w, -q, -l, color, map, rast, rules elif self.toComplete['entity'] == 'params+flags' and self.cmdDesc: self.autoCompList = list() for param in self.cmdDesc.get_options()['params']: self.autoCompList.append(param['name']) for flag in self.cmdDesc.get_options()['flags']: if len(flag['name']) == 1: self.autoCompList.append('-' + flag['name']) else: self.autoCompList.append('--' + flag['name']) self.ShowList() # complete map or values after parameter # r.buffer input=| ...list of raster maps # r.buffer units=| ... feet, kilometers, ... elif self.toComplete['entity'] == 'raster map': self.autoCompList = list() self.autoCompList = self.mapList['raster'] elif self.toComplete['entity'] == 'vector map': self.autoCompList = list() self.autoCompList = self.mapList['vector'] elif self.toComplete['entity'] == 'param values': self.autoCompList = list() param = self.GetWordLeft(withDelimiter = False, ignoredDelimiter='=').strip(' =') self.autoCompList = self.cmdDesc.get_param(param)['values'] self.ShowList() elif event.GetKeyCode() == wx.WXK_SPACE: items = self.GetTextLeft().split() if len(items) == 1: cmd = items[0].strip() if cmd in globalvar.grassCmd and \ (not self.cmdDesc or cmd != self.cmdDesc.get_name()): try: self.cmdDesc = gtask.parse_interface(cmd) except IOError: self.cmdDesc = None event.Skip() else: event.Skip() def ShowStatusText(self, text): """Requests showing of notification, e.g. showing in a statusbar.""" self.showNotification.emit(message=text) def GetTextLeft(self): """Returns all text left of the caret""" pos = self.GetCurrentPos() self.HomeExtend() entry = self.GetSelectedText() self.SetCurrentPos(pos) return entry def OnDestroy(self, event): """The clipboard contents can be preserved after the app has exited""" wx.TheClipboard.Flush() event.Skip() def OnCmdErase(self, event): """Erase command prompt""" self.Home() self.DelLineRight()
class GConsole(wx.EvtHandler): """ """ def __init__(self, guiparent=None, giface=None, ignoredCmdPattern=None): """ :param guiparent: parent window for created GUI objects :param lmgr: layer manager window (TODO: replace by giface) :param ignoredCmdPattern: regular expression specifying commads to be ignored (e.g. @c '^d\..*' for display commands) """ wx.EvtHandler.__init__(self) # Signal when some map is created or updated by a module. # attributes: name: map name, ltype: map type, self.mapCreated = Signal('GConsole.mapCreated') # emitted when map display should be re-render self.updateMap = Signal('GConsole.updateMap') # emitted when log message should be written self.writeLog = Signal('GConsole.writeLog') # emitted when command log message should be written self.writeCmdLog = Signal('GConsole.writeCmdLog') # emitted when warning message should be written self.writeWarning = Signal('GConsole.writeWarning') # emitted when error message should be written self.writeError = Signal('GConsole.writeError') self._guiparent = guiparent self._giface = giface self._ignoredCmdPattern = ignoredCmdPattern # create queues self.requestQ = Queue.Queue() self.resultQ = Queue.Queue() self.cmdOutputTimer = wx.Timer(self) self.Bind(wx.EVT_TIMER, self.OnProcessPendingOutputWindowEvents) self.Bind(EVT_CMD_RUN, self.OnCmdRun) self.Bind(EVT_CMD_DONE, self.OnCmdDone) self.Bind(EVT_CMD_ABORT, self.OnCmdAbort) # stream redirection self.cmdStdOut = GStdout(receiver=self) self.cmdStdErr = GStderr(receiver=self) # thread self.cmdThread = CmdThread(self, self.requestQ, self.resultQ) def Redirect(self): """Redirect stdout/stderr """ if Debug.GetLevel() == 0 and int(grass.gisenv().get('DEBUG', 0)) == 0: # don't redirect when debugging is enabled sys.stdout = self.cmdStdOut sys.stderr = self.cmdStdErr else: enc = locale.getdefaultlocale()[1] if enc: sys.stdout = codecs.getwriter(enc)(sys.__stdout__) sys.stderr = codecs.getwriter(enc)(sys.__stderr__) else: sys.stdout = sys.__stdout__ sys.stderr = sys.__stderr__ def WriteLog(self, text, style=None, wrap=None, notification=Notification.HIGHLIGHT): """Generic method for writing log message in given style :param text: text line :param notification: form of notification """ self.writeLog.emit(text=text, wrap=wrap, notification=notification) def WriteCmdLog(self, text, pid=None, notification=Notification.MAKE_VISIBLE): """Write message in selected style :param text: message to be printed :param pid: process pid or None :param notification: form of notification """ self.writeCmdLog.emit(text=text, pid=pid, notification=notification) def WriteWarning(self, text): """Write message in warning style""" self.writeWarning.emit(text=text) def WriteError(self, text): """Write message in error style""" self.writeError.emit(text=text) def RunCmd(self, command, compReg=True, skipInterface=False, onDone=None, onPrepare=None, userData=None, notification=Notification.MAKE_VISIBLE): """Run command typed into console command prompt (GPrompt). .. todo:: Document the other event. .. todo:: Solve problem with the other event (now uses gOutputText event but there is no text, use onPrepare handler instead?) Posts event EVT_IGNORED_CMD_RUN when command which should be ignored (according to ignoredCmdPattern) is run. For example, see layer manager which handles d.* on its own. :param command: command given as a list (produced e.g. by utils.split()) :param compReg: True use computation region :param notification: form of notification :param bool skipInterface: True to do not launch GRASS interface parser when command has no arguments given :param onDone: function to be called when command is finished :param onPrepare: function to be called before command is launched :param userData: data defined for the command """ if len(command) == 0: Debug.msg(2, "GPrompt:RunCmd(): empty command") return # update history file self.UpdateHistoryFile(' '.join(command)) if command[0] in globalvar.grassCmd: # send GRASS command without arguments to GUI command interface # except ignored commands (event is emitted) if self._ignoredCmdPattern and \ re.compile(self._ignoredCmdPattern).search(' '.join(command)) and \ '--help' not in command and '--ui' not in command: event = gIgnoredCmdRun(cmd=command) wx.PostEvent(self, event) return else: # other GRASS commands (r|v|g|...) try: task = GUI(show=None).ParseCommand(command) except GException as e: GError(parent=self._guiparent, message=unicode(e), showTraceback=False) return hasParams = False if task: options = task.get_options() hasParams = options['params'] and options['flags'] # check for <input>=- for p in options['params']: if p.get('prompt', '') == 'input' and \ p.get('element', '') == 'file' and \ p.get('age', 'new') == 'old' and \ p.get('value', '') == '-': GError(parent=self._guiparent, message=_("Unable to run command:\n%(cmd)s\n\n" "Option <%(opt)s>: read from standard input is not " "supported by wxGUI") % {'cmd': ' '.join(command), 'opt': p.get('name', '')}) return if len(command) == 1 and hasParams and \ command[0] != 'v.krige': # no arguments given try: GUI(parent=self._guiparent, giface=self._giface).ParseCommand(command) except GException as e: print >> sys.stderr, e return # activate computational region (set with g.region) # for all non-display commands. if compReg: tmpreg = os.getenv("GRASS_REGION") if "GRASS_REGION" in os.environ: del os.environ["GRASS_REGION"] # process GRASS command with argument self.cmdThread.RunCmd(command, stdout=self.cmdStdOut, stderr=self.cmdStdErr, onDone=onDone, onPrepare=onPrepare, userData=userData, env=os.environ.copy(), notification=notification) self.cmdOutputTimer.Start(50) # deactivate computational region and return to display settings if compReg and tmpreg: os.environ["GRASS_REGION"] = tmpreg else: # Send any other command to the shell. Send output to # console output window # # Check if the script has an interface (avoid double-launching # of the script) # check if we ignore the command (similar to grass commands part) if self._ignoredCmdPattern and \ re.compile(self._ignoredCmdPattern).search(' '.join(command)): event = gIgnoredCmdRun(cmd=command) wx.PostEvent(self, event) return skipInterface = True if os.path.splitext(command[0])[1] in ('.py', '.sh'): try: sfile = open(command[0], "r") for line in sfile.readlines(): if len(line) < 2: continue if line[0] is '#' and line[1] is '%': skipInterface = False break sfile.close() except IOError: pass if len(command) == 1 and not skipInterface: try: task = gtask.parse_interface(command[0]) except: task = None else: task = None if task: # process GRASS command without argument GUI(parent=self._guiparent, giface=self._giface).ParseCommand(command) else: self.cmdThread.RunCmd(command, stdout=self.cmdStdOut, stderr=self.cmdStdErr, onDone=onDone, onPrepare=onPrepare, userData=userData, notification=notification) self.cmdOutputTimer.Start(50) def GetLog(self, err=False): """Get widget used for logging .. todo:: what's this? :param bool err: True to get stderr widget """ if err: return self.cmdStdErr return self.cmdStdOut def GetCmd(self): """Get running command or None""" return self.requestQ.get() def OnCmdAbort(self, event): """Abort running command""" self.cmdThread.abort() event.Skip() def OnCmdRun(self, event): """Run command""" self.WriteCmdLog('(%s)\n%s' % (str(time.ctime()), ' '.join(event.cmd)), notification=event.notification) event.Skip() def OnCmdDone(self, event): """Command done (or aborted) Sends signal mapCreated if map is recognized in output parameters or for specific modules (as r.colors). """ # Process results here try: ctime = time.time() - event.time if ctime < 60: stime = _("%d sec") % int(ctime) else: mtime = int(ctime / 60) stime = _("%(min)d min %(sec)d sec") % {'min': mtime, 'sec': int(ctime - (mtime * 60))} except KeyError: # stopped deamon stime = _("unknown") if event.aborted: # Thread aborted (using our convention of None return) self.WriteWarning(_('Please note that the data are left in' ' inconsistent state and may be corrupted')) msg = _('Command aborted') else: msg = _('Command finished') self.WriteCmdLog('(%s) %s (%s)' % (str(time.ctime()), msg, stime), notification=event.notification) if event.onDone: event.onDone(cmd=event.cmd, returncode=event.returncode) self.cmdOutputTimer.Stop() if event.cmd[0] == 'g.gisenv': Debug.SetLevel() self.Redirect() # do nothing when no map added if event.returncode != 0 or event.aborted: event.Skip() return if event.cmd[0] not in globalvar.grassCmd: return # find which maps were created try: task = GUI(show=None).ParseCommand(event.cmd) except GException as e: print >> sys.stderr, e task = None return name = task.get_name() for p in task.get_options()['params']: prompt = p.get('prompt', '') if prompt in ('raster', 'vector', '3d-raster') and p.get('value', None): if p.get('age', 'old') == 'new' or \ name in ('r.colors', 'r3.colors', 'v.colors', 'v.proj', 'r.proj'): # if multiple maps (e.g. r.series.interp), we need add each if p.get('multiple', False): lnames = p.get('value').split(',') # in case multiple input (old) maps in r.colors # we don't want to rerender it multiple times! just once if p.get('age', 'old') == 'old': lnames = lnames[0:1] else: lnames = [p.get('value')] for lname in lnames: if '@' not in lname: lname += '@' + grass.gisenv()['MAPSET'] self.mapCreated.emit(name=lname, ltype=prompt) if name == 'r.mask': self.updateMap.emit() event.Skip() def OnProcessPendingOutputWindowEvents(self, event): wx.GetApp().ProcessPendingEvents() def UpdateHistoryFile(self, command): """Update history file :param command: the command given as a string """ env = grass.gisenv() try: filePath = os.path.join(env['GISDBASE'], env['LOCATION_NAME'], env['MAPSET'], '.bash_history') fileHistory = codecs.open(filePath, encoding='utf-8', mode='a') except IOError as e: GError(_("Unable to write file '%(filePath)s'.\n\nDetails: %(error)s") % {'filePath': filePath, 'error': e}, parent=self._guiparent) return try: fileHistory.write(command + os.linesep) finally: fileHistory.close() # update wxGUI prompt if self._giface: self._giface.UpdateCmdHistory(command)
class ScattsManager: """Main controller """ def __init__(self, guiparent, giface, iclass_mapwin=None): self.giface = giface self.mapDisp = giface.GetMapDisplay() if iclass_mapwin: self.mapWin = iclass_mapwin else: self.mapWin = giface.GetMapWindow() self.guiparent = guiparent self.show_add_scatt_plot = False self.core = Core() self.cats_mgr = CategoriesManager(self, self.core) self.render_mgr = PlotsRenderingManager(scatt_mgr=self, cats_mgr=self.cats_mgr, core=self.core) self.thread = gThread() self.plots = {} self.plot_mode = None self.pol_sel_mode = [False, None] self.data_set = False self.cursorPlotMove = Signal("ScattsManager.cursorPlotMove") self.renderingStarted = self.render_mgr.renderingStarted self.renderingFinished = self.render_mgr.renderingFinished self.computingStarted = Signal("ScattsManager.computingStarted") if iclass_mapwin: self.digit_conn = IClassDigitConnection(self, self.mapWin, self.core.CatRastUpdater()) self.iclass_conn = IClassConnection(self, iclass_mapwin.parent, self.cats_mgr) else: self.digit_conn = IMapWinDigitConnection() self.iclass_conn = IMapDispConnection(scatt_mgr=self, cats_mgr=self.cats_mgr, giface=self.giface) self._initSettings() self.modeSet = Signal("ScattsManager.mondeSet") def CleanUp(self): self.thread.Terminate() # there should be better way hot to clean up the thread # than calling the clean up function outside the thread, # which still may running self.core.CleanUp() def CleanUpDone(self): for scatt_id, scatt in self.plots.items(): if scatt['scatt']: scatt['scatt'].CleanUp() self.plots.clear() def _initSettings(self): """Initialization of settings (if not already defined) """ # initializes default settings initSettings = [ ['selection', 'sel_pol', (255, 255, 0)], ['selection', 'sel_pol_vertex', (255, 0, 0)], ['selection', 'sel_area', (0, 255, 19)], ['selection', "snap_tresh", 10], ['selection', 'sel_area_opacty', 50], ['ellipses', 'show_ellips', True], ] for init in initSettings: UserSettings.ReadSettingsFile() UserSettings.Append(dict=UserSettings.userSettings, group='scatt', key=init[0], subkey=init[1], value=init[2], overwrite=False) def SetData(self): self.iclass_conn.SetData() self.digit_conn.SetData() def SetBands(self, bands): self.busy = wx.BusyInfo(_("Loading data...")) self.data_set = False self.thread.Run(callable=self.core.CleanUp, ondone=lambda event: self.CleanUpDone()) if self.show_add_scatt_plot: show_add = True else: show_add = False self.all_bands_to_bands = dict(zip(bands, [-1] * len(bands))) self.all_bands = bands self.region = GetRegion() ncells = self.region["rows"] * self.region["cols"] if ncells > MAX_NCELLS: del self.busy self.data_set = True return self.bands = bands[:] self.bands_info = {} valid_bands = [] for b in self.bands[:]: i = GetRasterInfo(b) self.bands_info[b] = i if i is not None: valid_bands.append(b) for i, b in enumerate(valid_bands): # name : index in core bands - # if not in core bands (not CELL type) -> index = -1 self.all_bands_to_bands[b] = i self.thread.Run(callable=self.core.SetData, bands=valid_bands, ondone=self.SetDataDone, userdata={"show_add": show_add}) def SetDataDone(self, event): del self.busy self.data_set = True todo = event.ret self.bad_bands = event.ret bands = self.core.GetBands() self.bad_rasts = event.ret self.cats_mgr.SetData() if event.userdata['show_add']: self.AddScattPlot() def GetBands(self): return self.core.GetBands() def AddScattPlot(self): if not self.data_set and self.iclass_conn: self.show_add_scatt_plot = True self.iclass_conn.SetData() self.show_add_scatt_plot = False return if not self.data_set: GError(_('No data set.')) return self.computingStarted.emit() bands = self.core.GetBands() #added_bands_ids = [] # for scatt_id in self.plots): # added_bands_ids.append[idBandsToidScatt(scatt_id)] self.digit_conn.Update() ncells = self.region["rows"] * self.region["cols"] if ncells > MAX_NCELLS: GError( _( parent=self.guiparent, mmessage=_( "Interactive Scatter Plot Tool can not be used.\n" "Number of cells (rows*cols) <%d> in current region" "is higher than maximum limit <%d>.\n\n" "You can reduce number of cells in current region using <g.region> command." % (ncells, MAX_NCELLS)))) return elif ncells > WARN_NCELLS: dlg = wx.MessageDialog( parent=self.guiparent, message=_("Number of cells (rows*cols) <%d> in current region is " "higher than recommended threshold <%d>.\n" "It is strongly advised to reduce number of cells " "in current region below recommend threshold.\n " "It can be done by <g.region> command.\n\n" "Do you want to continue using " "Interactive Scatter Plot Tool with this region?" % (ncells, WARN_NCELLS)), style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_WARNING) ret = dlg.ShowModal() if ret != wx.ID_YES: return dlg = AddScattPlotDialog(parent=self.guiparent, bands=self.all_bands, check_bands_callback=self.CheckBands) if dlg.ShowModal() == wx.ID_OK: scatt_ids = [] sel_bands = dlg.GetBands() for b_1, b_2 in sel_bands: transpose = False if b_1 > b_2: transpose = True tmp_band = b_2 b_2 = b_1 b_1 = tmp_band b_1_id = self.all_bands_to_bands[self.all_bands[b_1]] b_2_id = self.all_bands_to_bands[self.all_bands[b_2]] scatt_id = idBandsToidScatt(b_1_id, b_2_id, len(bands)) if scatt_id in self.plots: continue self.plots[scatt_id] = {'transpose': transpose, 'scatt': None} scatt_ids.append(scatt_id) self._addScattPlot(scatt_ids) dlg.Destroy() def CheckBands(self, b_1, b_2): bands = self.core.GetBands() added_scatts_ids = self.plots.keys() b_1_id = self.all_bands_to_bands[self.all_bands[b_1]] b_2_id = self.all_bands_to_bands[self.all_bands[b_1]] scatt_id = idBandsToidScatt(b_1_id, b_2_id, len(bands)) if scatt_id in added_scatts_ids: GWarning( parent=self.guiparent, message=_( "Scatter plot with same band combination (regardless x y order) " "is already displayed.")) return False b_1_name = self.all_bands[b_1] b_2_name = self.all_bands[b_2] b_1_i = self.bands_info[b_1_name] b_2_i = self.bands_info[b_2_name] err = "" for b in [b_1_name, b_2_name]: if self.bands_info[b] is None: err += _("Band <%s> is not CELL (integer) type.\n" % b) if err: GMessage(parent=self.guiparent, message=_("Scatter plot cannot be added.\n" + err)) return False mrange = b_1_i['range'] * b_2_i['range'] if mrange > MAX_SCATT_SIZE: GWarning(parent=self.guiparent, message=_("Scatter plot cannot be added.\n" "Multiple of bands ranges <%s:%d * %s:%d = %d> " "is higher than maximum limit <%d>.\n" % (b_1_name, b_1_i['range'], b_1_name, b_2_i['range'], mrange, MAX_SCATT_SIZE))) return False elif mrange > WARN_SCATT_SIZE: dlg = wx.MessageDialog( parent=self.guiparent, message=_( "Multiple of bands ranges <%s:%d * %s:%d = %d> " "is higher than recommended limit <%d>.\n" "It is strongly advised to reduce range extend of bands" "(e. g. using r.rescale) below recommended threshold.\n\n" "Do you really want to add this scatter plot?" % (b_1_name, b_1_i['range'], b_1_name, b_2_i['range'], mrange, WARN_SCATT_SIZE)), style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_WARNING) ret = dlg.ShowModal() if ret != wx.ID_YES: return False return True def _addScattPlot(self, scatt_ids): self.render_mgr.NewRunningProcess() self.thread.Run(callable=self.core.AddScattPlots, scatt_ids=scatt_ids, ondone=self.AddScattPlotDone) def AddScattPlotDone(self, event): if not self.data_set: return scatt_ids = event.kwds['scatt_ids'] for s_id in scatt_ids: trans = self.plots[s_id]['transpose'] self.plots[s_id]['scatt'] = self.guiparent.NewScatterPlot( scatt_id=s_id, transpose=trans) self.plots[s_id]['scatt'].plotClosed.connect(self.PlotClosed) self.plots[s_id]['scatt'].cursorMove.connect( lambda x, y, scatt_id: self.cursorPlotMove.emit(x=x, y=y, scatt_id=scatt_id)) if self.plot_mode: self.plots[s_id]['scatt'].SetMode(self.plot_mode) self.plots[s_id]['scatt'].ZoomToExtend() self.render_mgr.RunningProcessDone() def PlotClosed(self, scatt_id): del self.plots[scatt_id] def SetPlotsMode(self, mode): self.plot_mode = mode for scatt in six.itervalues(self.plots): if scatt['scatt']: scatt['scatt'].SetMode(mode) self.modeSet.emit(mode=mode) def ActivateSelectionPolygonMode(self, activate): self.pol_sel_mode[0] = activate for scatt in six.itervalues(self.plots): if not scatt['scatt']: continue scatt['scatt'].SetSelectionPolygonMode(activate) if not activate and self.plot_mode not in [ 'zoom', 'pan', 'zoom_extend']: self.SetPlotsMode(None) self.render_mgr.RunningProcessDone() return activate def ProcessSelectionPolygons(self, process_mode): scatts_polygons = {} for scatt_id, scatt in six.iteritems(self.plots): if not scatt['scatt']: continue coords = scatt['scatt'].GetCoords() if coords is not None: scatts_polygons[scatt_id] = coords if not scatts_polygons: return value = 1 if process_mode == 'remove': value = 0 sel_cat_id = self.cats_mgr.GetSelectedCat() if not sel_cat_id: dlg = wx.MessageDialog( parent=self.guiparent, message=_( "In order to select arrea in scatter plot, " "you have to select class first.\n\n" "There is no class yet, " "do you want to create one?"), caption=_("No class selected"), style=wx.YES_NO) if dlg.ShowModal() == wx.ID_YES: self.iclass_conn.EmptyCategories() sel_cat_id = self.cats_mgr.GetSelectedCat() if not sel_cat_id: return for scatt in six.itervalues(self.plots): if scatt['scatt']: scatt['scatt'].SetEmpty() self.computingStarted.emit() self.render_mgr.NewRunningProcess() self.render_mgr.CategoryChanged(cat_ids=[sel_cat_id]) self.render_mgr.CategoryCondsChanged(cat_ids=[sel_cat_id]) self.thread.Run(callable=self.core.UpdateCategoryWithPolygons, cat_id=sel_cat_id, scatts_pols=scatts_polygons, value=value, ondone=self.SetEditCatDataDone) def SetEditCatDataDone(self, event): if not self.data_set: return self.render_mgr.RunningProcessDone() if event.exception: GError( _("Error occurred during computation of scatter plot category:\n%s"), parent=self.guiparent, showTraceback=False) cat_id = event.ret self.iclass_conn.RenderCatRast(cat_id) def SettingsUpdated(self, chanaged_setts): self.render_mgr.RenderRequest() #['ellipses', 'show_ellips'] def GetCategoriesManager(self): return self.cats_mgr
class Map(object): def __init__(self, gisrc = None): """Map composition (stack of map layers and overlays) :param gisrc: alternative gisrc (used eg. by georectifier) """ # region/extent settigns self.wind = dict() # WIND settings (wind file) self.region = dict() # region settings (g.region) self.width = 640 # map width self.height = 480 # map height # list of layers self.layers = list() # stack of available GRASS layer self.overlays = list() # stack of available overlays self.ovlookup = dict() # lookup dictionary for overlay items and overlays # path to external gisrc self.gisrc = gisrc # generated file for g.pnmcomp output for rendering the map self.mapfile = grass.tempfile(create = False) + '.ppm' # setting some initial env. variables if not self.GetWindow(): sys.stderr.write(_("Trying to recover from default region...")) RunCommand('g.region', flags='d') # info to report progress self.progressInfo = None # GRASS environment variable (for rendering) self.default_env = {"GRASS_RENDER_BACKGROUNDCOLOR" : "000000", "GRASS_RENDER_FILE_COMPRESSION" : "0", "GRASS_RENDER_TRUECOLOR" : "TRUE", "GRASS_RENDER_TRANSPARENT" : "TRUE" } # projection info self.projinfo = self._projInfo() # is some layer being downloaded? self.downloading = False self.layerChanged = Signal('Map.layerChanged') self.updateProgress = Signal('Map.updateProgress') def GetProjInfo(self): """Get projection info""" return self.projinfo def _projInfo(self): """Return region projection and map units information """ projinfo = dict() if not grass.find_program('g.proj', '--help'): sys.exit(_("GRASS module '%s' not found. Unable to start map " "display window.") % 'g.proj') env = os.environ.copy() if self.gisrc: env['GISRC'] = self.gisrc ret = RunCommand(prog='g.proj', read=True, flags='p', env=env) if not ret: return projinfo for line in ret.splitlines(): if ':' in line: key, val = map(lambda x: x.strip(), line.split(':')) if key in ['units']: val = val.lower() projinfo[key] = val elif "XY location (unprojected)" in line: projinfo['proj'] = 'xy' projinfo['units'] = '' break return projinfo def GetWindow(self): """Read WIND file and set up self.wind dictionary""" # FIXME: duplicated region WIND == g.region (at least some values) env = grass.gisenv() filename = os.path.join (env['GISDBASE'], env['LOCATION_NAME'], env['MAPSET'], "WIND") try: windfile = open (filename, "r") except IOError as e: sys.exit(_("Error: Unable to open '%(file)s'. Reason: %(ret)s. wxGUI exited.\n") % \ { 'file' : filename, 'ret' : e}) for line in windfile.readlines(): line = line.strip() try: key, value = line.split(":", 1) except ValueError as e: sys.stderr.write(_("\nERROR: Unable to read WIND file: %s\n") % e) return None self.wind[key.strip()] = value.strip() windfile.close() return self.wind def AdjustRegion(self): """Adjusts display resolution to match monitor size in pixels. Maintains constant display resolution, not related to computational region. Do NOT use the display resolution to set computational resolution. Set computational resolution through g.region. """ mapwidth = abs(self.region["e"] - self.region["w"]) mapheight = abs(self.region['n'] - self.region['s']) self.region["nsres"] = mapheight / self.height self.region["ewres"] = mapwidth / self.width self.region['rows'] = round(mapheight / self.region["nsres"]) self.region['cols'] = round(mapwidth / self.region["ewres"]) self.region['cells'] = self.region['rows'] * self.region['cols'] Debug.msg (3, "Map.AdjustRegion(): %s" % self.region) return self.region def AlignResolution(self): """Sets display extents to even multiple of current resolution defined in WIND file from SW corner. This must be done manually as using the -a flag can produce incorrect extents. """ # new values to use for saving to region file new = {} n = s = e = w = 0.0 nsres = ewres = 0.0 # Get current values for region and display reg = self.GetRegion() nsres = reg['nsres'] ewres = reg['ewres'] n = float(self.region['n']) s = float(self.region['s']) e = float(self.region['e']) w = float(self.region['w']) # Calculate rows, columns, and extents new['rows'] = math.fabs(round((n-s)/nsres)) new['cols'] = math.fabs(round((e-w)/ewres)) # Calculate new extents new['s'] = nsres * round(s / nsres) new['w'] = ewres * round(w / ewres) new['n'] = new['s'] + (new['rows'] * nsres) new['e'] = new['w'] + (new['cols'] * ewres) return new def AlignExtentFromDisplay(self): """Align region extent based on display size from center point""" # calculate new bounding box based on center of display if self.region["ewres"] > self.region["nsres"]: res = self.region["ewres"] else: res = self.region["nsres"] Debug.msg(3, "Map.AlignExtentFromDisplay(): width=%d, height=%d, res=%f, center=%f,%f" % \ (self.width, self.height, res, self.region['center_easting'], self.region['center_northing'])) ew = (self.width / 2) * res ns = (self.height / 2) * res self.region['n'] = self.region['center_northing'] + ns self.region['s'] = self.region['center_northing'] - ns self.region['e'] = self.region['center_easting'] + ew self.region['w'] = self.region['center_easting'] - ew # LL locations if self.projinfo['proj'] == 'll': self.region['n'] = min(self.region['n'], 90.0) self.region['s'] = max(self.region['s'], -90.0) def ChangeMapSize(self, size): """Change size of rendered map. :param size: map size given as tuple """ try: self.width = int(size[0]) self.height = int(size[1]) if self.width < 1 or self.height < 1: sys.stderr.write(_("Invalid map size %d,%d\n") % (self.width, self.height)) raise ValueError except ValueError: self.width = 640 self.height = 480 Debug.msg(2, "Map.ChangeMapSize(): width=%d, height=%d" % \ (self.width, self.height)) def GetRegion(self, rast=None, zoom=False, vect=None, rast3d=None, regionName=None, n=None, s=None, e=None, w=None, default=False, update=False, add3d=False): """Get region settings (g.region -upgc) Optionally extent, raster or vector map layer can be given. :param rast: list of raster maps :param zoom: zoom to raster map (ignore NULLs) :param vect: list of vector maps :param rast3d: 3d raster map (not list, no support of multiple 3d rasters in g.region) :param regionName: named region or None :param n,s,e,w: force extent :param default: force default region settings :param update: if True update current display region settings :param add3d: add 3d region settings :return: region settings as dictionary, e.g. { 'n':'4928010', 's':'4913700', 'w':'589980',...} :func:`GetCurrentRegion()` """ region = {} env = os.environ.copy() if self.gisrc: env['GISRC'] = self.gisrc # do not update & shell style output cmd = {} cmd['flags'] = 'ugpc' if default: cmd['flags'] += 'd' if add3d: cmd['flags'] += '3' if regionName: cmd['region'] = regionName if n: cmd['n'] = n if s: cmd['s'] = s if e: cmd['e'] = e if w: cmd['w'] = w if rast: if zoom: cmd['zoom'] = rast[0] else: cmd['raster'] = ','.join(rast) if vect: cmd['vector'] = ','.join(vect) if rast3d: cmd['raster_3d'] = rast3d ret, reg, msg = RunCommand('g.region', read = True, getErrorMsg = True, env=env, **cmd) if ret != 0: if rast: message = _("Unable to zoom to raster map <%s>.") % rast[0] + \ "\n\n" + _("Details:") + " %s" % msg elif vect: message = _("Unable to zoom to vector map <%s>.") % vect[0] + \ "\n\n" + _("Details:") + " %s" % msg elif rast3d: message = _("Unable to zoom to 3d raster map <%s>.") % rast3d + \ "\n\n" + _("Details:") + " %s" % msg else: message = _("Unable to get current geographic extent. " "Force quiting wxGUI. Please manually run g.region to " "fix the problem.") GError(message) return self.region for r in reg.splitlines(): key, val = r.split("=", 1) try: region[key] = float(val) except ValueError: region[key] = val Debug.msg (3, "Map.GetRegion(): %s" % region) if update: self.region = region return region def GetCurrentRegion(self): """Get current display region settings :func:`GetRegion()` """ return self.region def SetRegion(self, windres = False, windres3 = False): """Render string for GRASS_REGION env. variable, so that the images will be rendered from desired zoom level. :param windres: uses resolution from WIND file rather than display (for modules that require set resolution like d.rast.num) :return: String usable for GRASS_REGION variable or None """ grass_region = "" if windres: compRegion = self.GetRegion(add3d = windres3) region = copy.copy(self.region) for key in ('nsres', 'ewres', 'cells'): region[key] = compRegion[key] if windres3: for key in ('nsres3', 'ewres3', 'tbres', 'cells3', 'cols3', 'rows3', 'depths'): if key in compRegion: region[key] = compRegion[key] else: # adjust region settings to match monitor region = self.AdjustRegion() # read values from wind file try: for key in self.wind.keys(): if key == 'north': grass_region += "north: %s; " % \ (region['n']) continue elif key == "south": grass_region += "south: %s; " % \ (region['s']) continue elif key == "east": grass_region += "east: %s; " % \ (region['e']) continue elif key == "west": grass_region += "west: %s; " % \ (region['w']) continue elif key == "e-w resol": grass_region += "e-w resol: %f; " % \ (region['ewres']) continue elif key == "n-s resol": grass_region += "n-s resol: %f; " % \ (region['nsres']) continue elif key == "cols": if windres: continue grass_region += 'cols: %d; ' % \ region['cols'] continue elif key == "rows": if windres: continue grass_region += 'rows: %d; ' % \ region['rows'] continue elif key == "n-s resol3" and windres3: grass_region += "n-s resol3: %f; " % \ (region['nsres3']) elif key == "e-w resol3" and windres3: grass_region += "e-w resol3: %f; " % \ (region['ewres3']) elif key == "t-b resol" and windres3: grass_region += "t-b resol: %f; " % \ (region['tbres']) elif key == "cols3" and windres3: grass_region += "cols3: %d; " % \ (region['cols3']) elif key == "rows3" and windres3: grass_region += "rows3: %d; " % \ (region['rows3']) elif key == "depths" and windres3: grass_region += "depths: %d; " % \ (region['depths']) else: grass_region += key + ": " + self.wind[key] + "; " Debug.msg (3, "Map.SetRegion(): %s" % grass_region) return grass_region except: return None def GetListOfLayers(self, ltype = None, mapset = None, name = None, active = None, hidden = None): """Returns list of layers of selected properties or list of all layers. :param ltype: layer type, e.g. raster/vector/wms/overlay (value or tuple of values) :param mapset: all layers from given mapset (only for maplayers) :param name: all layers with given name :param active: only layers with 'active' attribute set to True or False :param hidden: only layers with 'hidden' attribute set to True or False :return: list of selected layers """ selected = [] if type(ltype) == types.StringType: one_type = True else: one_type = False if one_type and ltype == 'overlay': llist = self.overlays else: llist = self.layers # ["raster", "vector", "wms", ... ] for layer in llist: # specified type only if ltype != None: if one_type and layer.type != ltype: continue elif not one_type and layer.type not in ltype: continue # mapset if (mapset != None and ltype != 'overlay') and \ layer.GetMapset() != mapset: continue # name if name != None and layer.name != name: continue # hidden and active layers if active != None and \ hidden != None: if layer.active == active and \ layer.hidden == hidden: selected.append(layer) # active layers elif active != None: if layer.active == active: selected.append(layer) # hidden layers elif hidden != None: if layer.hidden == hidden: selected.append(layer) # all layers else: selected.append(layer) Debug.msg (3, "Map.GetListOfLayers(): numberof=%d" % len(selected)) return selected def _renderLayers(self, env, force = False, overlaysOnly = False): """Render all map layers into files :param bool force: True to force rendering :param bool overlaysOnly: True to render only overlays :return: list of maps, masks and opacities """ maps = list() masks = list() opacities = list() # render map layers if overlaysOnly: layers = self.overlays else: layers = self.layers + self.overlays self.downloading = False self.ReportProgress(layer=None) for layer in layers: # skip non-active map layers if not layer or not layer.active: continue # render if force or layer.forceRender: layer.SetEnvironment(env) if not layer.Render(): continue if layer.IsDownloading(): self.downloading = True self.ReportProgress(layer=layer) # skip map layers when rendering fails if not os.path.exists(layer.mapfile): continue # add image to compositing list if layer.type != "overlay": maps.append(layer.mapfile) masks.append(layer.maskfile) opacities.append(str(layer.opacity)) Debug.msg(3, "Map.Render() type=%s, layer=%s " % (layer.type, layer.name)) return maps, masks, opacities def GetMapsMasksAndOpacities(self, force, windres, env): """ Used by Render function. :return: maps, masks, opacities """ return self._renderLayers(force=force, env=env) def Render(self, force = False, windres = False): """Creates final image composite This function can conditionaly use high-level tools, which should be avaliable in wxPython library :param force: force rendering :param windres: use region resolution (True) otherwise display resolution :return: name of file with rendered image or None """ wx.BeginBusyCursor() env = os.environ.copy() env.update(self.default_env) # use external gisrc if defined if self.gisrc: env['GISRC'] = self.gisrc env['GRASS_REGION'] = self.SetRegion(windres) env['GRASS_RENDER_WIDTH'] = str(self.width) env['GRASS_RENDER_HEIGHT'] = str(self.height) driver = UserSettings.Get(group = 'display', key = 'driver', subkey = 'type') if driver == 'png': env['GRASS_RENDER_IMMEDIATE'] = 'png' else: env['GRASS_RENDER_IMMEDIATE'] = 'cairo' maps, masks, opacities = self.GetMapsMasksAndOpacities(force, windres, env) # ugly hack for MSYS if sys.platform != 'win32': mapstr = ",".join(maps) maskstr = ",".join(masks) else: mapstr = "" for item in maps: mapstr += item.replace('\\', '/') mapstr = mapstr.rstrip(',') maskstr = "" for item in masks: maskstr += item.replace('\\', '/') maskstr = maskstr.rstrip(',') # run g.pngcomp to get composite image bgcolor = ':'.join(map(str, UserSettings.Get(group = 'display', key = 'bgcolor', subkey = 'color'))) if maps: ret, msg = RunCommand('g.pnmcomp', getErrorMsg = True, overwrite = True, input = '%s' % ",".join(maps), mask = '%s' % ",".join(masks), opacity = '%s' % ",".join(opacities), bgcolor = bgcolor, width = self.width, height = self.height, output = self.mapfile, env=env) if ret != 0: print >> sys.stderr, _("ERROR: Rendering failed. Details: %s") % msg wx.EndBusyCursor() return None Debug.msg (3, "Map.Render() force=%s file=%s" % (force, self.mapfile)) wx.EndBusyCursor() if not maps: return None return self.mapfile def AddLayer(self, ltype, command, name = None, active = True, hidden = False, opacity = 1.0, render = False, pos = -1): """Adds generic map layer to list of layers :param ltype: layer type ('raster', 'vector', etc.) :param command: GRASS command given as list :param name: layer name :param active: layer render only if True :param hidden: layer not displayed in layer tree if True :param opacity: opacity level range from 0(transparent) - 1(not transparent) :param render: render an image if True :param pos: position in layer list (-1 for append) :return: new layer on success :return: None on failure """ wx.BeginBusyCursor() # opacity must be <0;1> if opacity < 0: opacity = 0 elif opacity > 1: opacity = 1 layer = MapLayer(ltype = ltype, name = name, cmd = command, Map = self, active = active, hidden = hidden, opacity = opacity) # add maplayer to the list of layers if pos > -1: self.layers.insert(pos, layer) else: self.layers.append(layer) Debug.msg (3, "Map.AddLayer(): layer=%s" % layer.name) if render: if not layer.Render(): raise GException(_("Unable to render map layer <%s>.") % name) renderMgr = layer.GetRenderMgr() if renderMgr: renderMgr.dataFetched.connect(self.layerChanged) renderMgr.updateProgress.connect(self.ReportProgress) wx.EndBusyCursor() return layer def DeleteAllLayers(self, overlay = False): """Delete all layers :param overlay: True to delete also overlayes """ self.layers = [] if overlay: self.overlays = [] def DeleteLayer(self, layer, overlay = False): """Removes layer from list of layers :param layer: layer instance in layer tree :param overlay: delete overlay (use self.DeleteOverlay() instead) :return: removed layer on success or None """ Debug.msg (3, "Map.DeleteLayer(): name=%s" % layer.name) if overlay: list = self.overlays else: list = self.layers if layer in list: if layer.mapfile: base = os.path.split(layer.mapfile)[0] mapfile = os.path.split(layer.mapfile)[1] tempbase = mapfile.split('.')[0] if base == '' or tempbase == '': return None basefile = os.path.join(base, tempbase) + r'.*' for f in glob.glob(basefile): os.remove(f) list.remove(layer) return layer return None def SetLayers(self, layers): self.layers = layers # only for debug # might be removed including message, it seems more than clear layerNameList = "" for layer in self.layers: if layer.GetName(): layerNameList += layer.GetName() + ',' Debug.msg(5, "Map.SetLayers(): layers=%s" % (layerNameList)) def ChangeLayer(self, layer, render = False, **kargs): """Change map layer properties :param layer: map layer instance :param ltype: layer type ('raster', 'vector', etc.) :param command: GRASS command given as list :param name: layer name :param active: layer render only if True :param hidden: layer not displayed in layer tree if True :param opacity: opacity level range from 0(transparent) - 1(not transparent) :param render: render an image if True """ Debug.msg (3, "Map.ChangeLayer(): layer=%s" % layer.name) if 'ltype' in kargs: layer.SetType(kargs['ltype']) # check type if 'command' in kargs: layer.SetCmd(kargs['command']) if 'name' in kargs: layer.SetName(kargs['name']) if 'active' in kargs: layer.SetActive(kargs['active']) if 'hidden' in kargs: layer.SetHidden(kargs['hidden']) if 'opacity' in kargs: layer.SetOpacity(kargs['opacity']) if render and not layer.Render(): raise GException(_("Unable to render map layer <%s>.") % layer.GetName()) return layer def ChangeOpacity(self, layer, opacity): """Changes opacity value of map layer :param layer: layer instance in layer tree :param opacity: opacity level <0;1> """ # opacity must be <0;1> if opacity < 0: opacity = 0 elif opacity > 1: opacity = 1 layer.opacity = opacity Debug.msg (3, "Map.ChangeOpacity(): layer=%s, opacity=%f" % \ (layer.name, layer.opacity)) def ChangeLayerActive(self, layer, active): """Enable or disable map layer :param layer: layer instance in layer tree :param active: to be rendered (True) """ layer.active = active Debug.msg (3, "Map.ChangeLayerActive(): name='%s' -> active=%d" % \ (layer.name, layer.active)) def ChangeLayerName (self, layer, name): """Change name of the layer :param layer: layer instance in layer tree :param name: layer name to set up """ Debug.msg (3, "Map.ChangeLayerName(): from=%s to=%s" % \ (layer.name, name)) layer.name = name def RemoveLayer(self, name = None, id = None): """Removes layer from layer list Layer is defined by name@mapset or id. :param name: layer name (must be unique) :param id: layer index in layer list def __init__(self, targetFile, region, bandsNum, gdalDriver, fillValue = None): :return: removed layer on success :return: None on failure """ # delete by name if name: retlayer = None for layer in self.layers: if layer.name == name: retlayer = layer os.remove(layer.mapfile) os.remove(layer.maskfile) self.layers.remove(layer) return retlayer # del by id elif id != None: return self.layers.pop(id) return None def GetLayerIndex(self, layer, overlay = False): """Get index of layer in layer list. :param layer: layer instace in layer tree :param overlay: use list of overlays instead :return: layer index :return: -1 if layer not found """ if overlay: list = self.overlays else: list = self.layers if layer in list: return list.index(layer) return -1 def AddOverlay(self, id, ltype, command, active = True, hidden = True, opacity = 1.0, render = False): """Adds overlay (grid, barscale, legend, etc.) to list of overlays :param id: overlay id (PseudoDC) :param ltype: overlay type (barscale, legend) :param command: GRASS command to render overlay :param active: overlay activated (True) or disabled (False) :param hidden: overlay is not shown in layer tree (if True) :param render: render an image (if True) :return: new layer on success :return: None on failure """ Debug.msg (2, "Map.AddOverlay(): cmd=%s, render=%d" % (command, render)) overlay = Overlay(id = id, ltype = ltype, cmd = command, Map = self, active = active, hidden = hidden, opacity = opacity) # add maplayer to the list of layers self.overlays.append(overlay) if render and command != '' and not overlay.Render(): raise GException(_("Unable to render overlay <%s>.") % ltype) return self.overlays[-1] def ChangeOverlay(self, id, render = False, **kargs): """Change overlay properities Add new overlay if overlay with 'id' doesn't exist. :param id: overlay id (PseudoDC) :param ltype: overlay ltype (barscale, legend) :param command: GRASS command to render overlay :param active: overlay activated (True) or disabled (False) :param hidden: overlay is not shown in layer tree (if True) :param render: render an image (if True) :return: new layer on success """ overlay = self.GetOverlay(id, list = False) if overlay is None: overlay = Overlay(id, ltype = None, cmd = None) if 'ltype' in kargs: overlay.SetName(kargs['ltype']) # ltype -> overlay if 'command' in kargs: overlay.SetCmd(kargs['command']) if 'active' in kargs: overlay.SetActive(kargs['active']) if 'hidden' in kargs: overlay.SetHidden(kargs['hidden']) if 'opacity' in kargs: overlay.SetOpacity(kargs['opacity']) if render and overlay.GetCmd() != [] and not overlay.Render(): raise GException(_("Unable to render overlay <%s>.") % overlay.GetType()) return overlay def GetOverlay(self, id, list=False): """Return overlay(s) with 'id' :param id: overlay id :param list: return list of overlays of True otherwise suppose 'id' to be unique :return: list of overlays (list=True) :return: overlay (list=False) :return: None (list=False) if no overlay or more overlays found """ ovl = [] for overlay in self.overlays: if overlay.id == id: ovl.append(overlay) if not list: if len(ovl) != 1: return None else: return ovl[0] return ovl def DeleteOverlay(self, overlay): """Delete overlay :param overlay: overlay layer :return: removed overlay on success or None """ return self.DeleteLayer(overlay, overlay = True) def _clean(self, llist): for layer in llist: if layer.maskfile: try_remove(layer.maskfile) if layer.mapfile: try_remove(layer.mapfile) llist.remove(layer) def Clean(self): """Clean layer stack - go trough all layers and remove them from layer list. Removes also mapfile and maskfile. """ self._clean(self.layers) self._clean(self.overlays) def ReverseListOfLayers(self): """Reverse list of layers""" return self.layers.reverse() def RenderOverlays(self, force): """Render overlays only (for nviz)""" for layer in self.overlays: if force or layer.forceRender: layer.Render() def AbortAllThreads(self): """Abort all layers threads e. g. donwloading data""" for l in self.layers + self.overlays: l.AbortThread() def ReportProgress(self, layer): """Calculates progress in rendering/downloading and emits signal to inform progress bar about progress. """ if self.progressInfo is None or layer is None: self.progressInfo = {'progresVal' : 0, # current progress value 'downloading' : [], # layers, which are downloading data 'rendered' : [], # already rendered layers 'range' : len(self.GetListOfLayers(active = True)) + len(self.GetListOfLayers(active = True, ltype = 'overlay')) - len(self.GetListOfLayers(active = True, ltype = '3d-raster'))} else: if layer not in self.progressInfo['rendered']: self.progressInfo['rendered'].append(layer) if layer.IsDownloading() and \ layer not in self.progressInfo['downloading']: self.progressInfo['downloading'].append(layer) else: self.progressInfo['progresVal'] += 1 if layer in self.progressInfo['downloading']: self.progressInfo['downloading'].remove(layer) # for updating statusbar text stText = '' first = True for layer in self.progressInfo['downloading']: if first: stText += _("Downloading data ") first = False else: stText += ', ' stText += '<%s>' % layer.GetName() if stText: stText += '...' if self.progressInfo['range'] != len(self.progressInfo['rendered']): if stText: stText = _('Rendering & ') + stText else: stText = _('Rendering...') self.updateProgress.emit(range=self.progressInfo['range'], value=self.progressInfo['progresVal'], text=stText)
class CategoriesManager: """Manages categories list of scatter plot. """ def __init__(self, scatt_mgr, core): self.core = core self.scatt_mgr = scatt_mgr self.cats = {} self.cats_ids = [] self.sel_cat_id = None self.exportRaster = None self.initialized = Signal('CategoriesManager.initialized') self.setCategoryAttrs = Signal('CategoriesManager.setCategoryAttrs') self.deletedCategory = Signal('CategoriesManager.deletedCategory') self.addedCategory = Signal('CategoriesManager.addedCategory') def ChangePosition(self, cat_id, new_pos): if new_pos >= len(self.cats_ids): return False try: pos = self.cats_ids.index(cat_id) except: return False if pos > new_pos: pos -= 1 self.cats_ids.remove(cat_id) self.cats_ids.insert(new_pos, cat_id) self.scatt_mgr.render_mgr.RenderRequest() return True def _addCategory(self, cat_id): self.scatt_mgr.thread.Run(callable=self.core.AddCategory, cat_id=cat_id) def SetData(self): if not self.scatt_mgr.data_set: return for cat_id in self.cats_ids: self.scatt_mgr.thread.Run(callable=self.core.AddCategory, cat_id=cat_id) def AddCategory(self, cat_id=None, name=None, color=None, nstd=None): if cat_id is None: if self.cats_ids: cat_id = max(self.cats_ids) + 1 else: cat_id = 1 if self.scatt_mgr.data_set: self.scatt_mgr.thread.Run(callable=self.core.AddCategory, cat_id=cat_id) # TODO check number of cats # if ret < 0: #TODO # return -1; self.cats[cat_id] = { 'name': 'class_%d' % cat_id, 'color': "0:0:0", 'opacity': 1.0, 'show': True, 'nstd': 1.0, } self.cats_ids.insert(0, cat_id) if name is not None: self.cats[cat_id]["name"] = name if color is not None: self.cats[cat_id]["color"] = color if nstd is not None: self.cats[cat_id]["nstd"] = nstd self.addedCategory.emit(cat_id=cat_id, name=self.cats[cat_id]["name"], color=self.cats[cat_id]["color"]) return cat_id def SetCategoryAttrs(self, cat_id, attrs_dict): render = False update_cat_rast = [] for k, v in six.iteritems(attrs_dict): if not render and k in ['color', 'opacity', 'show', 'nstd']: render = True if k in ['color', 'name']: update_cat_rast.append(k) self.cats[cat_id][k] = v if render: self.scatt_mgr.render_mgr.CategoryChanged(cat_ids=[cat_id]) self.scatt_mgr.render_mgr.RenderRequest() if update_cat_rast: self.scatt_mgr.iclass_conn.UpdateCategoryRaster( cat_id, update_cat_rast) self.setCategoryAttrs.emit(cat_id=cat_id, attrs_dict=attrs_dict) def DeleteCategory(self, cat_id): if self.scatt_mgr.data_set: self.scatt_mgr.thread.Run(callable=self.core.DeleteCategory, cat_id=cat_id) del self.cats[cat_id] self.cats_ids.remove(cat_id) self.deletedCategory.emit(cat_id=cat_id) # TODO emit event? def SetSelectedCat(self, cat_id): self.sel_cat_id = cat_id if self.scatt_mgr.pol_sel_mode[0]: self.scatt_mgr.render_mgr.RenderRequest() def GetSelectedCat(self): return self.sel_cat_id def GetCategoryAttrs(self, cat_id): #TODO is mutable return self.cats[cat_id] def GetCategoriesAttrs(self): #TODO is mutable return self.cats def GetCategories(self): return self.cats_ids[:] def ExportCatRast(self, cat_id): cat_attrs = self.GetCategoryAttrs(cat_id) dlg = ExportCategoryRaster( parent=self.scatt_mgr.guiparent, rasterName=self.exportRaster, title=_("Export scatter plot raster of class <%s>") % cat_attrs['name']) if dlg.ShowModal() == wx.ID_OK: self.exportCatRast = dlg.GetRasterName() dlg.Destroy() self.scatt_mgr.thread.Run(callable=self.core.ExportCatRast, userdata={'name': cat_attrs['name']}, cat_id=cat_id, rast_name=self.exportCatRast, ondone=self.OnExportCatRastDone) def OnExportCatRastDone(self, event): ret, err = event.ret if ret == 0: cat_attrs = self.GetCategoryAttrs(event.kwds['cat_id']) GMessage( _("Scatter plot raster of class <%s> exported to raster map <%s>.") % (event.userdata['name'], event.kwds['rast_name'])) else: GMessage( _("Export of scatter plot raster of class <%s> to map <%s> failed.\n%s") % (event.userdata['name'], event.kwds['rast_name'], err))
class RDigitController(wx.EvtHandler): """Controller object for raster digitizer. Inherits from EvtHandler to be able to send wx events from thraed. """ def __init__(self, giface, mapWindow): """Constructs controller :param giface: grass interface object :param mapWindow: instance of BufferedMapWindow """ wx.EvtHandler.__init__(self) self._giface = giface self._mapWindow = mapWindow # thread for running rasterization process self._thread = gThread() # name of raster map which is edited (also new one) self._editedRaster = None # name of optional background raster self._backgroundRaster = None # name of temporary raster used to backup original state self._backupRasterName = None # if we edit an old raster or a new one (important for setting color # table) self._editOldRaster = False # type of output raster map (CELL, FCELL, DCELL) self._mapType = None # GraphicsSet for drawing areas, lines, points self._areas = None self._lines = None self._points = None # list of all GraphicsItems in the order of drawing self._all = [] # if in state of drawing lin or area self._drawing = False # if running digitizing process in thread (to block drawing) self._running = False # color used to draw (should be moved to settings) self._drawColor = wx.GREEN # transparency used to draw (should be moved to settings) self._drawTransparency = 100 # current selected drawing method self._graphicsType = 'area' # last edited cell value self._currentCellValue = None # last edited buffer value self._currentWidthValue = None self._oldMouseUse = None self._oldCursor = None # signal to add new raster to toolbar items self.newRasterCreated = Signal('RDigitController:newRasterCreated') # signal to add just used cell value in toolbar combo self.newFeatureCreated = Signal('RDigitController:newFeatureCreated') # signal to upload unique categories of background map into toolbar # combo self.uploadMapCategories = Signal( 'RDigitController:uploadMapCategories') self.quitDigitizer = Signal('RDigitController:quitDigitizer') self.showNotification = Signal('RDigitController:showNotification') def _connectAll(self): self._mapWindow.mouseLeftDown.connect(self._start) self._mapWindow.mouseLeftUp.connect(self._addPoint) self._mapWindow.mouseRightUp.connect(self._finish) self._mapWindow.Unbind(wx.EVT_CONTEXT_MENU) def _disconnectAll(self): self._mapWindow.mouseLeftDown.disconnect(self._start) self._mapWindow.mouseLeftUp.disconnect(self._addPoint) self._mapWindow.mouseRightUp.disconnect(self._finish) self._mapWindow.Bind(wx.EVT_CONTEXT_MENU, self._mapWindow.OnContextMenu) def _start(self, x, y): """Start digitizing a new object. :param x: x coordinate in map units :param y: y coordinate in map units """ if self._running: return if not self._editedRaster: GMessage(parent=self._mapWindow, message=_("Please select first the raster map")) return if not self._drawing: if self._graphicsType == 'area': item = self._areas.AddItem(coords=[]) item.SetPropertyVal('penName', 'pen1') self._all.append(item) elif self._graphicsType == 'line': item = self._lines.AddItem(coords=[]) item.SetPropertyVal('penName', 'pen1') self._all.append(item) elif self._graphicsType == 'point': item = self._points.AddItem(coords=[]) item.SetPropertyVal('penName', 'pen1') self._all.append(item) self._drawing = True def _addPoint(self, x, y): """Add point to an object. :param x: x coordinate in map units :param y: y coordinate in map units """ if self._running: return if not self._drawing: return if self._graphicsType == 'area': area = self._areas.GetItem(-1) coords = area.GetCoords() + [[x, y]] area.SetCoords(coords) self.showNotification.emit(text=_("Right click to finish area")) elif self._graphicsType == 'line': line = self._lines.GetItem(-1) coords = line.GetCoords() + [[x, y]] line.SetCoords(coords) self.showNotification.emit(text=_("Right click to finish line")) elif self._graphicsType == 'point': point = self._points.GetItem(-1) point.SetCoords([x, y]) self._finish() # draw self._mapWindow.ClearLines() self._lines.Draw() self._areas.Draw() self._points.Draw() self._mapWindow.Refresh() def _finish(self): """Finish digitizing a new object and redraws. Saves current cell value and buffer width for that object. :param x: x coordinate in map units :param y: y coordinate in map units """ if self._running: return if self._graphicsType == 'point': item = self._points.GetItem(-1) elif self._graphicsType == 'area': item = self._areas.GetItem(-1) elif self._graphicsType == 'line': item = self._lines.GetItem(-1) else: return self._drawing = False item.SetPropertyVal('brushName', 'done') item.AddProperty('cellValue') item.AddProperty('widthValue') item.SetPropertyVal('cellValue', self._currentCellValue) item.SetPropertyVal('widthValue', self._currentWidthValue) self.newFeatureCreated.emit() self._mapWindow.ClearLines() self._points.Draw() self._areas.Draw() self._lines.Draw() self._mapWindow.Refresh() def SelectType(self, drawingType): """Selects method (area/line/point) for drawing. Connects and disconnects signal to allow other tools in map toolbar to work. """ if self._graphicsType and drawingType and self._graphicsType != drawingType \ and self._drawing: # if we select different drawing tool, finish the feature self._finish() if self._graphicsType and not drawingType: self._mapWindow.ClearLines(pdc=self._mapWindow.pdcTmp) self._mapWindow.mouse['end'] = self._mapWindow.mouse['begin'] # disconnect mouse events self._disconnectAll() self._mapWindow.SetNamedCursor(self._oldCursor) self._mapWindow.mouse['use'] = self._oldMouseUse elif self._graphicsType is None and drawingType: self._connectAll() # change mouse['box'] and pen to draw line during dragging # TODO: better solution for drawing this line self._mapWindow.mouse['use'] = None self._mapWindow.mouse['box'] = "line" self._mapWindow.pen = wx.Pen(colour='red', width=2, style=wx.SHORT_DASH) # change the cursor self._mapWindow.SetNamedCursor('pencil') self._graphicsType = drawingType def SetCellValue(self, value): self._currentCellValue = value def SetWidthValue(self, value): self._currentWidthValue = value def ChangeDrawColor(self, color): self._drawColor = color[:3] + (self._drawTransparency, ) for each in (self._areas, self._lines, self._points): each.GetPen('pen1').SetColour(self._drawColor) each.GetBrush('done').SetColour(self._drawColor) self._mapWindow.UpdateMap(render=False) def Start(self): """Registers graphics to map window, connect required mouse signals. """ self._oldMouseUse = self._mapWindow.mouse['use'] self._oldCursor = self._mapWindow.GetNamedCursor() self._connectAll() # change mouse['box'] and pen to draw line during dragging # TODO: better solution for drawing this line self._mapWindow.mouse['use'] = None self._mapWindow.mouse['box'] = "line" self._mapWindow.pen = wx.Pen(colour='red', width=2, style=wx.SHORT_DASH) color = self._drawColor[:3] + (self._drawTransparency, ) self._areas = self._mapWindow.RegisterGraphicsToDraw( graphicsType='polygon', pdc=self._mapWindow.pdcTransparent, mapCoords=True) self._areas.AddPen('pen1', wx.Pen(colour=color, width=2, style=wx.SOLID)) self._areas.AddBrush('done', wx.Brush(colour=color, style=wx.SOLID)) self._lines = self._mapWindow.RegisterGraphicsToDraw( graphicsType='line', pdc=self._mapWindow.pdcTransparent, mapCoords=True) self._lines.AddPen('pen1', wx.Pen(colour=color, width=2, style=wx.SOLID)) self._lines.AddBrush('done', wx.Brush(colour=color, style=wx.SOLID)) self._points = self._mapWindow.RegisterGraphicsToDraw( graphicsType='point', pdc=self._mapWindow.pdcTransparent, mapCoords=True) self._points.AddPen('pen1', wx.Pen(colour=color, width=2, style=wx.SOLID)) self._points.AddBrush('done', wx.Brush(colour=color, style=wx.SOLID)) # change the cursor self._mapWindow.SetNamedCursor('pencil') def Stop(self): """Before stopping digitizer, asks to save edits""" dlg = wx.MessageDialog(self._mapWindow, _("Do you want to save changes?"), _("Save raster map changes"), wx.YES_NO) if dlg.ShowModal() == wx.ID_YES: if self._drawing: self._finish() self._thread.Run(callable=self._exportRaster, ondone=lambda event: self._updateAndQuit()) else: self.quitDigitizer.emit() def Save(self): """Saves current edits to a raster map""" if self._drawing: self._finish() self._thread.Run(callable=self._exportRaster, ondone=lambda event: self._update()) def Undo(self): """Undo a change, goes object back (finished or not finished)""" if len(self._all): removed = self._all.pop(-1) # try to remove from each, it fails quietly when theitem is not # there self._areas.DeleteItem(removed) self._lines.DeleteItem(removed) self._points.DeleteItem(removed) self._drawing = False self._mapWindow.UpdateMap(render=False) def CleanUp(self, restore=True): """Cleans up drawing, temporary maps. :param restore: if restore previous cursor, mouse['use'] """ try: gcore.run_command('g.remove', type='raster', flags='f', name=self._backupRasterName, quiet=True) except CalledModuleError: pass self._mapWindow.ClearLines(pdc=self._mapWindow.pdcTmp) self._mapWindow.mouse['end'] = self._mapWindow.mouse['begin'] # disconnect mouse events if self._graphicsType: self._disconnectAll() # unregister self._mapWindow.UnregisterGraphicsToDraw(self._areas) self._mapWindow.UnregisterGraphicsToDraw(self._lines) self._mapWindow.UnregisterGraphicsToDraw(self._points) #self._registeredGraphics = None self._mapWindow.UpdateMap(render=False) if restore: # restore mouse['use'] and cursor to the state before measuring # starts self._mapWindow.SetNamedCursor(self._oldCursor) self._mapWindow.mouse['use'] = self._oldMouseUse def _updateAndQuit(self): """Called when thread is done. Updates map and calls to quits digitizer.""" self._running = False self._mapWindow.UpdateMap(render=True) self.quitDigitizer.emit() def _update(self): """Called when thread is done. Updates map.""" self._running = False self._mapWindow.UpdateMap(render=True) def SelectOldMap(self, name): """After selecting old raster, creates a backup copy for editing.""" try: self._backupRaster(name) except ScriptError: GError(parent=self._mapWindow, message=_( "Failed to create backup copy of edited raster map.")) return False self._editedRaster = name self._mapType = grast.raster_info(map=name)['datatype'] self._editOldRaster = True return True def SelectNewMap(self): """After selecting new raster, shows dialog to choose name, background map and type of the new map.""" dlg = NewRasterDialog(parent=self._mapWindow) dlg.CenterOnParent() if dlg.ShowModal() == wx.ID_OK: try: self._createNewMap(mapName=dlg.GetMapName(), backgroundMap=dlg.GetBackgroundMapName(), mapType=dlg.GetMapType()) except ScriptError: GError(parent=self._mapWindow, message=_("Failed to create new raster map.")) return False finally: dlg.Destroy() return True else: dlg.Destroy() return False def _createNewMap(self, mapName, backgroundMap, mapType): """Creates a new raster map based on specified background and type.""" name = mapName.split('@')[0] background = backgroundMap.split('@')[0] types = {'CELL': 'int', 'FCELL': 'float', 'DCELL': 'double'} if background: back = background else: back = 'null()' try: grast.mapcalc(exp="{name} = {mtype}({back})".format( name=name, mtype=types[mapType], back=back), overwrite=True, quiet=True) if background: self._backgroundRaster = backgroundMap gcore.run_command('r.colors', map=name, raster=self._backgroundRaster, quiet=True) if mapType == 'CELL': values = gcore.read_command('r.describe', flags='1n', map=name, quiet=True).strip() if values: self.uploadMapCategories.emit( values=values.split('\n')) except CalledModuleError: raise ScriptError self._backupRaster(name) name = name + '@' + gcore.gisenv()['MAPSET'] self._editedRaster = name self._mapType = mapType self.newRasterCreated.emit(name=name) def _backupRaster(self, name): """Creates a temporary backup raster necessary for undo behavior. :param str name: name of raster map for which we create backup """ name = name.split('@')[0] backup = name + '_backupcopy_' + str(os.getpid()) try: gcore.run_command('g.copy', raster=[name, backup], quiet=True) except CalledModuleError: raise ScriptError self._backupRasterName = backup def _exportRaster(self): """Rasterizes digitized features. Uses r.in.poly and r.grow for buffering features. Creates separate raster maps depending on common cell values and buffering width necessary to keep the order of editing. These rasters are then patched together. Sets default color table for the newly digitized raster. """ if not self._editedRaster or self._running: return self._running = True if len(self._all) < 1: new = self._editedRaster if '@' in self._editedRaster: new = self._editedRaster.split('@')[0] gcore.run_command('g.copy', raster=[self._backupRasterName, new], overwrite=True, quiet=True) else: tempRaster = 'tmp_rdigit_rast_' + str(os.getpid()) text = [] rastersToPatch = [] i = 0 lastCellValue = lastWidthValue = None evt = updateProgress(range=len(self._all), value=0, text=_("Rasterizing...")) wx.PostEvent(self, evt) lastCellValue = self._all[0].GetPropertyVal('cellValue') lastWidthValue = self._all[0].GetPropertyVal('widthValue') for item in self._all: if item.GetPropertyVal('widthValue') and \ (lastCellValue != item.GetPropertyVal('cellValue') or lastWidthValue != item.GetPropertyVal('widthValue')): if text: out = self._rasterize(text, lastWidthValue, self._mapType, tempRaster) rastersToPatch.append(out) text = [] self._writeItem(item, text) out = self._rasterize(text, item.GetPropertyVal('widthValue'), self._mapType, tempRaster) rastersToPatch.append(out) text = [] else: self._writeItem(item, text) lastCellValue = item.GetPropertyVal('cellValue') lastWidthValue = item.GetPropertyVal('widthValue') i += 1 evt = updateProgress(range=len(self._all), value=i, text=_("Rasterizing...")) wx.PostEvent(self, evt) if text: out = self._rasterize(text, item.GetPropertyVal('widthValue'), self._mapType, tempRaster) rastersToPatch.append(out) gcore.run_command('r.patch', input=rastersToPatch[::-1] + [self._backupRasterName], output=self._editedRaster, overwrite=True, quiet=True) gcore.run_command('g.remove', type='raster', flags='f', name=rastersToPatch + [tempRaster], quiet=True) try: # setting the right color table if self._editOldRaster: return if not self._backgroundRaster: table = UserSettings.Get(group='rasterLayer', key='colorTable', subkey='selection') if not table: table = 'rainbow' gcore.run_command('r.colors', color=table, map=self._editedRaster, quiet=True) else: gcore.run_command('r.colors', map=self._editedRaster, raster=self._backgroundRaster, quiet=True) except CalledModuleError: self._running = False GError( parent=self._mapWindow, message=_( "Failed to set default color table for edited raster map")) def _writeFeature(self, item, vtype, text): """Writes digitized features in r.in.poly format.""" coords = item.GetCoords() if vtype == 'P': coords = [coords] cellValue = item.GetPropertyVal('cellValue') record = '{vtype}\n'.format(vtype=vtype) for coord in coords: record += ' '.join([str(c) for c in coord]) record += '\n' record += '= {cellValue}\n'.format(cellValue=cellValue) text.append(record) def _writeItem(self, item, text): if item in self._areas.GetAllItems(): self._writeFeature(item, vtype='A', text=text) elif item in self._lines.GetAllItems(): self._writeFeature(item, vtype='L', text=text) elif item in self._points.GetAllItems(): self._writeFeature(item, vtype='P', text=text) def _rasterize(self, text, bufferDist, mapType, tempRaster): """Performs the actual rasterization using r.in.poly and buffering with r.grow if required. :param str text: string in r.in.poly format :param float bufferDist: buffer distance in map units :param str mapType: CELL, FCELL, DCELL :param str tempRaster: name of temporary raster used in computation :return: output raster map name as a result of digitization """ output = 'x' + str(uuid.uuid4())[:8] asciiFile = tempfile.NamedTemporaryFile(mode='w', delete=False) asciiFile.write('\n'.join(text)) asciiFile.close() if bufferDist: bufferDist /= 2. gcore.run_command('r.in.poly', input=asciiFile.name, output=tempRaster, type_=mapType, overwrite=True, quiet=True) gcore.run_command('r.grow', input=tempRaster, output=output, flags='m', radius=bufferDist, quiet=True) else: gcore.run_command('r.in.poly', input=asciiFile.name, output=output, type_=mapType, quiet=True) os.unlink(asciiFile.name) return output
class BitmapProvider: """Class for management of image files and bitmaps. There is one instance of this class in the application. It handles both 2D and 3D animations. """ def __init__(self, bitmapPool, mapFilesPool, tempDir, imageWidth=640, imageHeight=480): self._bitmapPool = bitmapPool self._mapFilesPool = mapFilesPool self.imageWidth = ( imageWidth # width of the image to render with d.rast or d.vect ) # height of the image to render with d.rast or d.vect self.imageHeight = imageHeight self._tempDir = tempDir self._uniqueCmds = [] self._cmdsForComposition = [] self._opacities = [] self._cmds3D = [] self._regionFor3D = None self._regions = [] self._regionsForUniqueCmds = [] self._renderer = BitmapRenderer(self._mapFilesPool, self._tempDir, self.imageWidth, self.imageHeight) self._composer = BitmapComposer( self._tempDir, self._mapFilesPool, self._bitmapPool, self.imageWidth, self.imageHeight, ) self.renderingStarted = Signal("BitmapProvider.renderingStarted") self.compositionStarted = Signal("BitmapProvider.compositionStarted") self.renderingContinues = Signal("BitmapProvider.renderingContinues") self.compositionContinues = Signal( "BitmapProvider.compositionContinues") self.renderingFinished = Signal("BitmapProvider.renderingFinished") self.compositionFinished = Signal("BitmapProvider.compositionFinished") self.mapsLoaded = Signal("BitmapProvider.mapsLoaded") self._renderer.renderingContinues.connect(self.renderingContinues) self._composer.compositionContinues.connect(self.compositionContinues) def SetCmds(self, cmdsForComposition, opacities, regions=None): """Sets commands to be rendered with opacity levels. Applies to 2D mode. :param cmdsForComposition: list of lists of command lists [[['d.rast', 'map=elev_2001'], ['d.vect', 'map=points']], # g.pnmcomp [['d.rast', 'map=elev_2002'], ['d.vect', 'map=points']], ...] :param opacities: list of opacity values :param regions: list of regions """ Debug.msg( 2, "BitmapProvider.SetCmds: {n} lists".format( n=len(cmdsForComposition))) self._cmdsForComposition.extend(cmdsForComposition) self._opacities.extend(opacities) self._regions.extend(regions) self._getUniqueCmds() def SetCmds3D(self, cmds, region): """Sets commands for 3D rendering. :param cmds: list of commands m.nviz.image (cmd as a list) :param region: for 3D rendering """ Debug.msg(2, "BitmapProvider.SetCmds3D: {c} commands".format(c=len(cmds))) self._cmds3D = cmds self._regionFor3D = region def _getUniqueCmds(self): """Returns list of unique commands. Takes into account the region assigned.""" unique = list() for cmdList, region in zip(self._cmdsForComposition, self._regions): for cmd in cmdList: if region: unique.append((tuple(cmd), tuple(sorted(region.items())))) else: unique.append((tuple(cmd), None)) unique = list(set(unique)) self._uniqueCmds = [cmdAndRegion[0] for cmdAndRegion in unique] self._regionsForUniqueCmds.extend([ dict(cmdAndRegion[1]) if cmdAndRegion[1] else None for cmdAndRegion in unique ]) def Unload(self): """Unloads currently loaded data. Needs to be called before setting new data. """ Debug.msg(2, "BitmapProvider.Unload") if self._cmdsForComposition: for cmd, region in zip(self._uniqueCmds, self._regionsForUniqueCmds): del self._mapFilesPool[HashCmd(cmd, region)] for cmdList, region in zip(self._cmdsForComposition, self._regions): del self._bitmapPool[HashCmds(cmdList, region)] self._uniqueCmds = [] self._cmdsForComposition = [] self._opacities = [] self._regions = [] self._regionsForUniqueCmds = [] if self._cmds3D: self._cmds3D = [] self._regionFor3D = None def _dryRender(self, uniqueCmds, regions, force): """Determines how many files will be rendered. :param uniqueCmds: list of commands which are to be rendered :param force: if forced rerendering :param regions: list of regions assigned to the commands """ count = 0 for cmd, region in zip(uniqueCmds, regions): filename = GetFileFromCmd(self._tempDir, cmd, region) if (not force and os.path.exists(filename) and self._mapFilesPool.GetSize(HashCmd( cmd, region)) == (self.imageWidth, self.imageHeight)): continue count += 1 Debug.msg( 3, "BitmapProvider._dryRender: {c} files to be rendered".format( c=count)) return count def _dryCompose(self, cmdLists, regions, force): """Determines how many lists of (commands) files will be composed (with g.pnmcomp). :param cmdLists: list of commands lists which are to be composed :param regions: list of regions assigned to the commands :param force: if forced rerendering """ count = 0 for cmdList, region in zip(cmdLists, regions): if (not force and HashCmds(cmdList, region) in self._bitmapPool and self._bitmapPool[HashCmds(cmdList, region)].GetSize() == (self.imageWidth, self.imageHeight)): continue count += 1 Debug.msg( 2, "BitmapProvider._dryCompose: {c} files to be composed".format( c=count)) return count def Load(self, force=False, bgcolor=(255, 255, 255), nprocs=4): """Loads data, both 2D and 3D. In case of 2D, it creates composites, even when there is only 1 layer to compose (to be changed for speedup) :param force: if True reload all data, otherwise only missing data :param bgcolor: background color as a tuple of 3 values 0 to 255 :param nprocs: number of procs to be used for rendering """ Debug.msg( 2, "BitmapProvider.Load: " "force={f}, bgcolor={b}, nprocs={n}".format(f=force, b=bgcolor, n=nprocs), ) cmds = [] regions = [] if self._uniqueCmds: cmds.extend(self._uniqueCmds) regions.extend(self._regionsForUniqueCmds) if self._cmds3D: cmds.extend(self._cmds3D) regions.extend([None] * len(self._cmds3D)) count = self._dryRender(cmds, regions, force=force) self.renderingStarted.emit(count=count) # create no data bitmap if None not in self._bitmapPool or force: self._bitmapPool[None] = createNoDataBitmap( self.imageWidth, self.imageHeight) ok = self._renderer.Render( cmds, regions, regionFor3D=self._regionFor3D, bgcolor=bgcolor, force=force, nprocs=nprocs, ) self.renderingFinished.emit() if not ok: self.mapsLoaded.emit() # what to do here? return if self._cmdsForComposition: count = self._dryCompose(self._cmdsForComposition, self._regions, force=force) self.compositionStarted.emit(count=count) self._composer.Compose( self._cmdsForComposition, self._regions, self._opacities, bgcolor=bgcolor, force=force, nprocs=nprocs, ) self.compositionFinished.emit() if self._cmds3D: for cmd in self._cmds3D: self._bitmapPool[HashCmds([cmd], None)] = wx.Bitmap( GetFileFromCmd(self._tempDir, cmd, None)) self.mapsLoaded.emit() def RequestStopRendering(self): """Requests to stop rendering/composition""" Debug.msg(2, "BitmapProvider.RequestStopRendering") self._renderer.RequestStopRendering() self._composer.RequestStopComposing() def GetBitmap(self, dataId): """Returns bitmap with given key or 'no data' bitmap if no such key exists. :param dataId: name of bitmap """ try: bitmap = self._bitmapPool[dataId] except KeyError: bitmap = self._bitmapPool[None] return bitmap def WindowSizeChanged(self, width, height): """Sets size when size of related window changes.""" Debug.msg( 5, "BitmapProvider.WindowSizeChanged: w={w}, h={h}".format(w=width, h=height), ) self.imageWidth, self.imageHeight = width, height self._composer.imageWidth = self._renderer.imageWidth = width self._composer.imageHeight = self._renderer.imageHeight = height def LoadOverlay(self, cmd): """Creates raster legend with d.legend :param cmd: d.legend command as a list :return: bitmap with legend """ Debug.msg(5, "BitmapProvider.LoadOverlay: cmd={c}".format(c=cmd)) fileHandler, filename = tempfile.mkstemp(suffix=".png") os.close(fileHandler) # Set the environment variables for this process _setEnvironment( self.imageWidth, self.imageHeight, filename, transparent=True, bgcolor=(0, 0, 0), ) Debug.msg(1, "Render raster legend " + str(filename)) cmdTuple = cmdlist_to_tuple(cmd) returncode, stdout, messages = read2_command(cmdTuple[0], **cmdTuple[1]) if returncode == 0: return BitmapFromImage(autoCropImageFromFile(filename)) else: os.remove(filename) raise GException(messages)
class SwipeMapDialog(wx.Dialog): """Dialog used to select maps. There are two modes - simple (only two raster maps), or two layer lists. """ def __init__(self, parent, title=_("Select raster maps"), first=None, second=None, firstLayerList=None, secondLayerList=None): wx.Dialog.__init__(self, parent=parent, title=title, style=wx.RESIZE_BORDER | wx.DEFAULT_DIALOG_STYLE) if firstLayerList is None: self._firstLayerList = LayerList() else: self._firstLayerList = copy.deepcopy(firstLayerList) if secondLayerList is None: self._secondLayerList = LayerList() else: self._secondLayerList = copy.deepcopy(secondLayerList) self._firstPanel = self._createSimplePanel() self._secondPanel = self._createAdvancedPanel() self.btnSwitch = wx.Button(self) self.btnCancel = wx.Button(self, id=wx.ID_CANCEL) self.btnApply = wx.Button(self, id=wx.ID_APPLY) self.btnOK = wx.Button(self, id=wx.ID_OK) self.btnOK.SetDefault() self.btnSwitch.Bind(wx.EVT_BUTTON, self.OnSwitchMode) self.btnApply.Bind(wx.EVT_BUTTON, lambda evt: self._apply()) self.btnOK.Bind(wx.EVT_BUTTON, lambda evt: self._ok()) self.btnCancel.Bind(wx.EVT_BUTTON, lambda evt: self.Close()) self.Bind(wx.EVT_CLOSE, lambda evt: self.Hide()) self.applyChanges = Signal('SwipeMapDialog.applyChanges') if first: self._firstRaster.SetValue(first) if second: self._secondRaster.SetValue(second) self._layout() def _layout(self): """Do layout""" mainSizer = wx.BoxSizer(wx.VERTICAL) self._switchSizer = wx.BoxSizer() self._switchSizer.Add(self._firstPanel, proportion=1, flag=wx.EXPAND | wx.ALL, border=5) self._switchSizer.Add(self._secondPanel, proportion=1, flag=wx.EXPAND | wx.ALL, border=5) mainSizer.Add(self._switchSizer, proportion=1, flag=wx.EXPAND | wx.ALL) self.btnSizer = wx.StdDialogButtonSizer() self.btnSizer.AddButton(self.btnCancel) self.btnSizer.AddButton(self.btnOK) self.btnSizer.AddButton(self.btnApply) self.btnSizer.Realize() mainSizer.Add(item=self.btnSwitch, proportion=0, flag=wx.ALL | wx.ALIGN_LEFT, border=5) mainSizer.Add(item=self.btnSizer, proportion=0, flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5) self.mainSizer = mainSizer self.SetSizer(mainSizer) mainSizer.Fit(self) self._switchMode(simple=True) def _createSimplePanel(self): panel = wx.Panel(self) sizer = wx.BoxSizer(wx.VERTICAL) self._firstRaster = gselect.Select( parent=panel, type='raster', size=globalvar.DIALOG_GSELECT_SIZE, validator=SimpleValidator(callback=self.ValidatorCallback)) self._secondRaster = gselect.Select( parent=panel, type='raster', size=globalvar.DIALOG_GSELECT_SIZE, validator=SimpleValidator(callback=self.ValidatorCallback)) sizer.Add(wx.StaticText(panel, label=_("Name of top/left raster map:")), proportion=0, flag=wx.EXPAND | wx.ALL, border=5) sizer.Add(self._firstRaster, proportion=0, flag=wx.EXPAND | wx.ALL, border=1) sizer.Add(wx.StaticText(panel, label=_("Name of bottom/right raster map:")), proportion=0, flag=wx.EXPAND | wx.ALL, border=1) sizer.Add(self._secondRaster, proportion=0, flag=wx.EXPAND | wx.ALL, border=1) self._firstRaster.SetFocus() panel.SetSizer(sizer) sizer.Fit(panel) return panel def _createAdvancedPanel(self): panel = wx.Panel(self) sizer = wx.BoxSizer(wx.HORIZONTAL) self._firstLmgr = SimpleLayerManager( parent=panel, layerList=self._firstLayerList, lmgrStyle=SIMPLE_LMGR_RASTER | SIMPLE_LMGR_RGB | SIMPLE_LMGR_VECTOR | SIMPLE_LMGR_TB_LEFT) self._secondLmgr = SimpleLayerManager( parent=panel, layerList=self._secondLayerList, lmgrStyle=SIMPLE_LMGR_RASTER | SIMPLE_LMGR_RGB | SIMPLE_LMGR_VECTOR | SIMPLE_LMGR_TB_RIGHT) sizer.Add(self._firstLmgr, proportion=1, flag=wx.EXPAND | wx.ALL, border=5) sizer.Add(self._secondLmgr, proportion=1, flag=wx.EXPAND | wx.ALL, border=5) panel.SetSizer(sizer) sizer.Fit(panel) return panel def _switchMode(self, simple): if simple: self._switchSizer.Show(self._firstPanel, show=True, recursive=True) self._switchSizer.Show(self._secondPanel, show=False, recursive=True) self.btnSwitch.SetLabel(_("Switch to advanced mode")) self.btnCancel.SetLabel(_("Cancel")) else: self._switchSizer.Show(self._firstPanel, show=False, recursive=True) self._switchSizer.Show(self._secondPanel, show=True, recursive=True) self.btnSwitch.SetLabel(_("Switch to simple mode")) self.btnCancel.SetLabel(_("Close")) self.Freeze() # doesn't do anything (at least on Ubuntu) self.btnSizer.Show(self.btnApply, simple) self.btnSizer.Show(self.btnOK, simple) self.btnSizer.Layout() self._switchSizer.Layout() self.Fit() self.Thaw() self.applyChanges.emit() def OnSwitchMode(self, event): if self._switchSizer.IsShown(self._secondPanel): self._switchMode(simple=True) else: self._switchMode(simple=False) def ValidatorCallback(self, win): if self._switchSizer.IsShown(self._secondPanel): return if win == self._firstRaster.GetTextCtrl(): GMessage(parent=self, message=_("Name of the first map is missing.")) else: GMessage(parent=self, message=_("Name of the second map is missing.")) def _ok(self): self._apply() self.Close() def _apply(self): # TODO check if not empty self.applyChanges.emit() def GetValues(self): """Get raster maps""" if self.IsSimpleMode(): return (self._firstRaster.GetValue(), self._secondRaster.GetValue()) else: return (self._firstLayerList, self._secondLayerList) def IsSimpleMode(self): if self._switchSizer.IsShown(self._firstPanel): return True return False def GetFirstSimpleLmgr(self): return self._firstLmgr def GetSecondSimpleLmgr(self): return self._secondLmgr
class GConsoleWindow(wx.SplitterWindow): """Create and manage output console for commands run by GUI.""" def __init__( self, parent, giface, gconsole, menuModel=None, margin=False, style=wx.TAB_TRAVERSAL | wx.FULL_REPAINT_ON_RESIZE, gcstyle=GC_EMPTY, **kwargs, ): """ :param parent: gui parent :param gconsole: console logic :param menuModel: tree model of modules (from menu) :param margin: use margin in output pane (GStc) :param style: wx.SplitterWindow style :param gcstyle: GConsole style (GC_EMPTY, GC_PROMPT to show command prompt) """ wx.SplitterWindow.__init__(self, parent, id=wx.ID_ANY, style=style, **kwargs) self.SetName("GConsole") self.panelOutput = wx.Panel(parent=self, id=wx.ID_ANY) self.panelProgress = wx.Panel( parent=self.panelOutput, id=wx.ID_ANY, name="progressPanel" ) self.panelPrompt = wx.Panel(parent=self, id=wx.ID_ANY) # initialize variables self.parent = parent # GMFrame | CmdPanel | ? self._gconsole = gconsole self._menuModel = menuModel self._gcstyle = gcstyle self.lineWidth = 80 # signal which requests showing of a notification self.showNotification = Signal("GConsoleWindow.showNotification") # signal emitted when text appears in the console # parameter 'notification' suggests form of notification (according to # core.giface.Notification) self.contentChanged = Signal("GConsoleWindow.contentChanged") # progress bar self.progressbar = wx.Gauge( parent=self.panelProgress, id=wx.ID_ANY, range=100, pos=(110, 50), size=(-1, 25), style=wx.GA_HORIZONTAL, ) self._gconsole.Bind(EVT_CMD_PROGRESS, self.OnCmdProgress) self._gconsole.Bind(EVT_CMD_OUTPUT, self.OnCmdOutput) self._gconsole.Bind(EVT_CMD_RUN, self.OnCmdRun) self._gconsole.Bind(EVT_CMD_DONE, self.OnCmdDone) self._gconsole.writeLog.connect(self.WriteLog) self._gconsole.writeCmdLog.connect(self.WriteCmdLog) self._gconsole.writeWarning.connect(self.WriteWarning) self._gconsole.writeError.connect(self.WriteError) # text control for command output self.cmdOutput = GStc( parent=self.panelOutput, id=wx.ID_ANY, margin=margin, wrap=None ) # command prompt # move to the if below # search depends on cmd prompt self.cmdPrompt = GPromptSTC( parent=self, giface=giface, menuModel=self._menuModel ) self.cmdPrompt.promptRunCmd.connect( lambda cmd: self._gconsole.RunCmd(command=cmd) ) self.cmdPrompt.showNotification.connect(self.showNotification) if not self._gcstyle & GC_PROMPT: self.cmdPrompt.Hide() if self._gcstyle & GC_PROMPT: cmdLabel = _("Command prompt") self.outputBox = StaticBox( parent=self.panelOutput, id=wx.ID_ANY, label=" %s " % _("Output window") ) self.cmdBox = StaticBox( parent=self.panelOutput, id=wx.ID_ANY, label=" %s " % cmdLabel ) # buttons self.btnOutputClear = ClearButton(parent=self.panelOutput) self.btnOutputClear.SetToolTip(_("Clear output window content")) self.btnCmdClear = ClearButton(parent=self.panelOutput) self.btnCmdClear.SetToolTip(_("Clear command prompt content")) self.btnOutputSave = Button(parent=self.panelOutput, id=wx.ID_SAVE) self.btnOutputSave.SetToolTip(_("Save output window content to the file")) self.btnCmdAbort = Button(parent=self.panelProgress, id=wx.ID_STOP) self.btnCmdAbort.SetToolTip(_("Abort running command")) self.btnCmdProtocol = ToggleButton( parent=self.panelOutput, id=wx.ID_ANY, label=_("&Log file"), size=self.btnCmdClear.GetSize(), ) self.btnCmdProtocol.SetToolTip( _( "Toggle to save list of executed commands into " "a file; content saved when switching off." ) ) self.cmdFileProtocol = None if not self._gcstyle & GC_PROMPT: self.btnCmdClear.Hide() self.btnCmdProtocol.Hide() self.btnCmdClear.Bind(wx.EVT_BUTTON, self.cmdPrompt.OnCmdErase) self.btnOutputClear.Bind(wx.EVT_BUTTON, self.OnOutputClear) self.btnOutputSave.Bind(wx.EVT_BUTTON, self.OnOutputSave) self.btnCmdAbort.Bind(wx.EVT_BUTTON, self._gconsole.OnCmdAbort) self.btnCmdProtocol.Bind(wx.EVT_TOGGLEBUTTON, self.OnCmdProtocol) self._layout() def _layout(self): """Do layout""" self.outputSizer = wx.BoxSizer(wx.VERTICAL) progressSizer = wx.BoxSizer(wx.HORIZONTAL) btnSizer = wx.BoxSizer(wx.HORIZONTAL) if self._gcstyle & GC_PROMPT: outBtnSizer = wx.StaticBoxSizer(self.outputBox, wx.HORIZONTAL) cmdBtnSizer = wx.StaticBoxSizer(self.cmdBox, wx.HORIZONTAL) else: outBtnSizer = wx.BoxSizer(wx.HORIZONTAL) cmdBtnSizer = wx.BoxSizer(wx.HORIZONTAL) if self._gcstyle & GC_PROMPT: promptSizer = wx.BoxSizer(wx.VERTICAL) promptSizer.Add( self.cmdPrompt, proportion=1, flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border=3, ) helpText = StaticText( self.panelPrompt, id=wx.ID_ANY, label="Press Tab to display command help, Ctrl+Space to autocomplete", ) helpText.SetForegroundColour( wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT) ) promptSizer.Add(helpText, proportion=0, flag=wx.EXPAND | wx.LEFT, border=5) self.outputSizer.Add( self.cmdOutput, proportion=1, flag=wx.EXPAND | wx.ALL, border=3 ) if self._gcstyle & GC_PROMPT: proportion = 1 else: proportion = 0 outBtnSizer.AddStretchSpacer() outBtnSizer.Add( self.btnOutputClear, proportion=proportion, flag=wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.BOTTOM, border=5, ) outBtnSizer.Add( self.btnOutputSave, proportion=proportion, flag=wx.RIGHT | wx.BOTTOM, border=5, ) cmdBtnSizer.Add( self.btnCmdProtocol, proportion=1, flag=wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT | wx.BOTTOM, border=5, ) cmdBtnSizer.Add( self.btnCmdClear, proportion=1, flag=wx.ALIGN_CENTER | wx.RIGHT | wx.BOTTOM, border=5, ) progressSizer.Add( self.btnCmdAbort, proportion=0, flag=wx.ALL | wx.ALIGN_CENTER, border=5 ) progressSizer.Add( self.progressbar, proportion=1, flag=wx.ALIGN_CENTER | wx.RIGHT | wx.TOP | wx.BOTTOM, border=5, ) self.panelProgress.SetSizer(progressSizer) progressSizer.Fit(self.panelProgress) btnSizer.Add(outBtnSizer, proportion=1, flag=wx.ALL | wx.ALIGN_CENTER, border=5) btnSizer.Add( cmdBtnSizer, proportion=1, flag=wx.ALIGN_CENTER | wx.TOP | wx.BOTTOM | wx.RIGHT, border=5, ) self.outputSizer.Add(self.panelProgress, proportion=0, flag=wx.EXPAND) self.outputSizer.Add(btnSizer, proportion=0, flag=wx.EXPAND) self.outputSizer.Fit(self) self.outputSizer.SetSizeHints(self) self.panelOutput.SetSizer(self.outputSizer) self.outputSizer.FitInside(self.panelOutput) if self._gcstyle & GC_PROMPT: promptSizer.Fit(self) promptSizer.SetSizeHints(self) self.panelPrompt.SetSizer(promptSizer) # split window if self._gcstyle & GC_PROMPT: self.SplitHorizontally(self.panelOutput, self.panelPrompt, -50) else: self.SplitHorizontally(self.panelOutput, self.panelPrompt, -45) self.Unsplit() self.SetMinimumPaneSize(self.btnCmdClear.GetSize()[1] + 25) self.SetSashGravity(1.0) self.outputSizer.Hide(self.panelProgress) # layout self.SetAutoLayout(True) self.Layout() def GetPanel(self, prompt=True): """Get panel :param prompt: get prompt / output panel :return: wx.Panel reference """ if prompt: return self.panelPrompt return self.panelOutput def WriteLog( self, text, style=None, wrap=None, notification=Notification.HIGHLIGHT ): """Generic method for writing log message in given style. Emits contentChanged signal. :param line: text line :param style: text style (see GStc) :param stdout: write to stdout or stderr :param notification: form of notification """ self.cmdOutput.SetStyle() # documenting old behavior/implementation: # switch notebook if required # now, let user to bind to the old event if not style: style = self.cmdOutput.StyleDefault # p1 = self.cmdOutput.GetCurrentPos() p1 = self.cmdOutput.GetEndStyled() # self.cmdOutput.GotoPos(p1) self.cmdOutput.DocumentEnd() for line in text.splitlines(): # fill space if len(line) < self.lineWidth: diff = self.lineWidth - len(line) line += diff * " " self.cmdOutput.AddTextWrapped(line, wrap=wrap) # adds '\n' p2 = self.cmdOutput.GetCurrentPos() # between wxWidgets 3.0 and 3.1 they dropped mask param try: self.cmdOutput.StartStyling(p1) except TypeError: self.cmdOutput.StartStyling(p1, 0xFF) self.cmdOutput.SetStyling(p2 - p1, style) self.cmdOutput.EnsureCaretVisible() self.contentChanged.emit(notification=notification) def WriteCmdLog(self, text, pid=None, notification=Notification.MAKE_VISIBLE): """Write message in selected style :param text: message to be printed :param pid: process pid or None :param switchPage: True to switch page """ if pid: text = "(" + str(pid) + ") " + text self.WriteLog( text, style=self.cmdOutput.StyleCommand, notification=notification ) def WriteWarning(self, text): """Write message in warning style""" self.WriteLog( text, style=self.cmdOutput.StyleWarning, notification=Notification.MAKE_VISIBLE, ) def WriteError(self, text): """Write message in error style""" self.WriteLog( text, style=self.cmdOutput.StyleError, notification=Notification.MAKE_VISIBLE, ) def OnOutputClear(self, event): """Clear content of output window""" self.cmdOutput.SetReadOnly(False) self.cmdOutput.ClearAll() self.cmdOutput.SetReadOnly(True) self.progressbar.SetValue(0) def GetProgressBar(self): """Return progress bar widget""" return self.progressbar def OnOutputSave(self, event): """Save (selected) text from output window to the file""" text = self.cmdOutput.GetSelectedText() if not text: text = self.cmdOutput.GetText() # add newline if needed if len(text) > 0 and text[-1] != "\n": text += "\n" dlg = wx.FileDialog( self, message=_("Save file as..."), defaultFile="grass_cmd_output.txt", wildcard=_("%(txt)s (*.txt)|*.txt|%(files)s (*)|*") % {"txt": _("Text files"), "files": _("Files")}, style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, ) # Show the dialog and retrieve the user response. If it is the OK response, # process the data. if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() try: output = open(path, "w") output.write(text) except IOError as e: GError( _("Unable to write file '%(path)s'.\n\nDetails: %(error)s") % {"path": path, "error": e} ) finally: output.close() message = _("Command output saved into '%s'") % path self.showNotification.emit(message=message) dlg.Destroy() def SetCopyingOfSelectedText(self, copy): """Enable or disable copying of selected text in to clipboard. Effects prompt and output. :param bool copy: True for enable, False for disable """ if copy: self.cmdPrompt.Bind( stc.EVT_STC_PAINTED, self.cmdPrompt.OnTextSelectionChanged ) self.cmdOutput.Bind( stc.EVT_STC_PAINTED, self.cmdOutput.OnTextSelectionChanged ) else: self.cmdPrompt.Unbind(stc.EVT_STC_PAINTED) self.cmdOutput.Unbind(stc.EVT_STC_PAINTED) def OnCmdOutput(self, event): """Prints command output. Emits contentChanged signal. """ message = event.text type = event.type self.cmdOutput.AddStyledMessage(message, type) if event.type in ("warning", "error"): self.contentChanged.emit(notification=Notification.MAKE_VISIBLE) else: self.contentChanged.emit(notification=Notification.HIGHLIGHT) def OnCmdProgress(self, event): """Update progress message info""" self.progressbar.SetValue(event.value) event.Skip() def CmdProtocolSave(self): """Save list of manually entered commands into a text log file""" if self.cmdFileProtocol is None: return # it should not happen try: with open(self.cmdFileProtocol, "a") as output: cmds = self.cmdPrompt.GetCommands() output.write("\n".join(cmds)) if len(cmds) > 0: output.write("\n") except IOError as e: GError( _("Unable to write file '{filePath}'.\n\nDetails: {error}").format( filePath=self.cmdFileProtocol, error=e ) ) self.showNotification.emit( message=_("Command log saved to '{}'".format(self.cmdFileProtocol)) ) self.cmdFileProtocol = None def OnCmdProtocol(self, event=None): """Save commands into file""" if not event.IsChecked(): # stop capturing commands, save list of commands to the # protocol file self.CmdProtocolSave() else: # start capturing commands self.cmdPrompt.ClearCommands() # ask for the file dlg = wx.FileDialog( self, message=_("Save file as..."), defaultFile="grass_cmd_log.txt", wildcard=_("%(txt)s (*.txt)|*.txt|%(files)s (*)|*") % {"txt": _("Text files"), "files": _("Files")}, style=wx.FD_SAVE, ) if dlg.ShowModal() == wx.ID_OK: self.cmdFileProtocol = dlg.GetPath() else: wx.CallAfter(self.btnCmdProtocol.SetValue, False) dlg.Destroy() event.Skip() def OnCmdRun(self, event): """Run command""" self.outputSizer.Show(self.panelProgress) self.outputSizer.Layout() event.Skip() def OnCmdDone(self, event): """Command done (or aborted)""" self.progressbar.SetValue(0) # reset progress bar on '0%' wx.CallLater(100, self._hideProgress) event.Skip() def _hideProgress(self): self.outputSizer.Hide(self.panelProgress) self.outputSizer.Layout() def ResetFocus(self): """Reset focus""" self.cmdPrompt.SetFocus() def GetPrompt(self): """Get prompt""" return self.cmdPrompt
class MapWindowBase(object): """Abstract map display window class Superclass for BufferedWindow class (2D display mode), and GLWindow (3D display mode). Subclasses have to define - _bindMouseEvents method which binds MouseEvent handlers - Pixel2Cell - Cell2Pixel (if it is possible) """ def __init__(self, parent, giface, Map): self.parent = parent self.Map = Map self._giface = giface # Emitted when someone registers as mouse event handler self.mouseHandlerRegistered = Signal( 'MapWindow.mouseHandlerRegistered') # Emitted when mouse event handler is unregistered self.mouseHandlerUnregistered = Signal( 'MapWindow.mouseHandlerUnregistered') # emitted after double click in pointer mode on legend, text, scalebar self.overlayActivated = Signal('MapWindow.overlayActivated') # emitted when overlay should be hidden self.overlayRemoved = Signal('MapWindow.overlayRemoved') # mouse attributes -- position on the screen, begin and end of # dragging, and type of drawing self.mouse = { 'begin': [0, 0], # screen coordinates 'end': [0, 0], 'use': "pointer", 'box': "point" } # last east, north coordinates, changes on mouse motion self.lastEN = None # stores overridden cursor self._overriddenCursor = None # dictionary where event types are stored as keys and lists of # handlers for these types as values self.handlersContainer = { wx.EVT_LEFT_DOWN: [], wx.EVT_LEFT_UP: [], wx.EVT_LEFT_DCLICK: [], wx.EVT_MIDDLE_DOWN: [], wx.EVT_MIDDLE_UP: [], wx.EVT_MIDDLE_DCLICK: [], wx.EVT_RIGHT_DOWN: [], wx.EVT_RIGHT_UP: [], wx.EVT_RIGHT_DCLICK: [], wx.EVT_MOTION: [], wx.EVT_ENTER_WINDOW: [], wx.EVT_LEAVE_WINDOW: [], wx.EVT_MOUSEWHEEL: [], wx.EVT_MOUSE_EVENTS: [] } # available cursors self._cursors = { "default": wx.StockCursor(wx.CURSOR_ARROW), "cross": wx.StockCursor(wx.CURSOR_CROSS), "hand": wx.StockCursor(wx.CURSOR_HAND), "pencil": wx.StockCursor(wx.CURSOR_PENCIL), "sizenwse": wx.StockCursor(wx.CURSOR_SIZENWSE) } # default cursor for window is arrow (at least we rely on it here) # but we need to define attribute here # cannot call SetNamedCursor since it expects the instance # to be a wx window, so setting only the attribute self._cursor = 'default' wx.CallAfter(self.InitBinding) def __del__(self): self.UnregisterAllHandlers() def InitBinding(self): """Binds helper functions, which calls all handlers registered to events with the events """ for ev, handlers in self.handlersContainer.iteritems(): self.Bind(ev, self.EventTypeHandler(handlers)) def EventTypeHandler(self, evHandlers): return lambda event: self.HandlersCaller(event, evHandlers) def HandlersCaller(self, event, handlers): """Hepler function which calls all handlers registered for event """ for handler in handlers: try: handler(event) except: handlers.remove(handler) GError( parent=self, message=_("Error occurred during calling of handler: %s \n" "Handler was unregistered.") % handler.__name__) event.Skip() def RegisterMouseEventHandler(self, event, handler, cursor=None): """Binds event handler @depreciated This method is depreciated. Use Signals or drawing API instead. Signals do not cover all events but new Signals can be added when needed consider also adding generic signal. However, more interesing and useful is higher level API to create objects, graphics etc. Call event.Skip() in handler to allow default processing in MapWindow. If any error occurs inside of handler, the handler is removed. Before handler is unregistered it is called with string value "unregistered" of event parameter. :: # your class methods def OnButton(self, event): # current map display's map window # expects LayerManager to be the parent self.mapwin = self.parent.GetLayerTree().GetMapDisplay().GetWindow() if self.mapwin.RegisterEventHandler(wx.EVT_LEFT_DOWN, self.OnMouseAction, 'cross'): self.parent.GetLayerTree().GetMapDisplay().Raise() else: # handle that you cannot get coordinates def OnMouseAction(self, event): # get real world coordinates of mouse click coor = self.mapwin.Pixel2Cell(event.GetPositionTuple()[:]) self.text.SetLabel('Coor: ' + str(coor)) self.mapwin.UnregisterMouseEventHandler(wx.EVT_LEFT_DOWN, self.OnMouseAction) event.Skip() Emits mouseHandlerRegistered signal before handler is registered. :param event: one of mouse events :param handler: function to handle event :param cursor: cursor which temporary overrides current cursor :return: True if successful :return: False if event cannot be bind """ self.mouseHandlerRegistered.emit() # inserts handler into list for containerEv, handlers in self.handlersContainer.iteritems(): if event == containerEv: handlers.append(handler) self.mouse['useBeforeGenericEvent'] = self.mouse['use'] self.mouse['use'] = 'genericEvent' if cursor: self._overriddenCursor = self.GetNamedCursor() self.SetNamedCursor(cursor) return True def UnregisterAllHandlers(self): """Unregisters all registered handlers @depreciated This method is depreciated. Use Signals or drawing API instead. Before each handler is unregistered it is called with string value "unregistered" of event parameter. """ for containerEv, handlers in self.handlersContainer.iteritems(): for handler in handlers: try: handler("unregistered") handlers.remove(handler) except: GError( parent=self, message= _("Error occurred during unregistration of handler: %s \n \ Handler was unregistered.") % handler.__name__) handlers.remove(handler) def UnregisterMouseEventHandler(self, event, handler): """Unbinds event handler for event @depreciated This method is depreciated. Use Signals or drawing API instead. Before handler is unregistered it is called with string value "unregistered" of event parameter. Emits mouseHandlerUnregistered signal after handler is unregistered. :param handler: handler to unbind :param event: event from which handler will be unbinded :return: True if successful :return: False if event cannot be unbind """ # removes handler from list for containerEv, handlers in self.handlersContainer.iteritems(): if event != containerEv: continue try: handler("unregistered") if handler in handlers: handlers.remove(handler) else: grass.warning( _("Handler: %s was not registered") % handler.__name__) except: GError( parent=self, message= _("Error occurred during unregistration of handler: %s \n \ Handler was unregistered") % handler.__name__) handlers.remove(handler) # restore mouse use (previous state) self.mouse['use'] = self.mouse['useBeforeGenericEvent'] # restore overridden cursor if self._overriddenCursor: self.SetNamedCursor(self._overriddenCursor) self.mouseHandlerUnregistered.emit() return True def Pixel2Cell(self, xyCoords): raise NotImplementedError() def Cell2Pixel(self, enCoords): raise NotImplementedError() def OnMotion(self, event): """Tracks mouse motion and update statusbar .. todo:: remove this method when lastEN is not used :func:`GetLastEN` """ try: self.lastEN = self.Pixel2Cell(event.GetPositionTuple()) except (ValueError): self.lastEN = None event.Skip() def GetLastEN(self): """Returns last coordinates of mouse cursor. @depreciated This method is depreciated. Use Signal with coordinates as parameters. :func:`OnMotion` """ return self.lastEN def SetNamedCursor(self, cursorName): """Sets cursor defined by name.""" cursor = self._cursors[cursorName] self.SetCursor(cursor) self._cursor = cursorName def GetNamedCursor(self): """Returns current cursor name.""" return self._cursor cursor = property(fget=GetNamedCursor, fset=SetNamedCursor) def SetModePointer(self): """Sets mouse mode to pointer.""" self.mouse['use'] = 'pointer' self.mouse['box'] = 'point' self.SetNamedCursor('default') def SetModePan(self): """Sets mouse mode to pan.""" self.mouse['use'] = "pan" self.mouse['box'] = "box" self.zoomtype = 0 self.SetNamedCursor('hand') def SetModeZoomIn(self): self._setModeZoom(zoomType=1) def SetModeZoomOut(self): self._setModeZoom(zoomType=-1) def _setModeZoom(self, zoomType): self.zoomtype = zoomType self.mouse['use'] = "zoom" self.mouse['box'] = "box" self.pen = wx.Pen(colour='Red', width=2, style=wx.SHORT_DASH) self.SetNamedCursor('cross') def SetModeDrawRegion(self): self.mouse['use'] = 'drawRegion' self.mouse['box'] = "box" self.pen = wx.Pen(colour='Red', width=2, style=wx.SHORT_DASH) self.SetNamedCursor('cross') def SetModeQuery(self): """Query mode on""" self.mouse['use'] = "query" self.mouse['box'] = "point" self.zoomtype = 0 self.SetNamedCursor('cross') def DisactivateWin(self): """Use when the class instance is hidden in MapFrame.""" raise NotImplementedError() def ActivateWin(self): """Used when the class instance is activated in MapFrame.""" raise NotImplementedError()
class MapWindowProperties(object): def __init__(self): self._resolution = None self.resolutionChanged = Signal( 'MapWindowProperties.resolutionChanged') self._autoRender = None self.autoRenderChanged = Signal( 'MapWindowProperties.autoRenderChanged') self._showRegion = None self.showRegionChanged = Signal( 'MapWindowProperties.showRegionChanged') self._alignExtent = None self.alignExtentChanged = Signal( 'MapWindowProperties.alignExtentChanged') def setValuesFromUserSettings(self): """Convenient function to get values from user settings into this object.""" self._resolution = UserSettings.Get(group='display', key='compResolution', subkey='enabled') self._autoRender = UserSettings.Get(group='display', key='autoRendering', subkey='enabled') self._showRegion = False # in statusbar.py was not from settings self._alignExtent = UserSettings.Get(group='display', key='alignExtent', subkey='enabled') @property def resolution(self): return self._resolution @resolution.setter def resolution(self, value): if value != self._resolution: self._resolution = value self.resolutionChanged.emit(value=value) @property def autoRender(self): return self._autoRender @autoRender.setter def autoRender(self, value): if value != self._autoRender: self._autoRender = value self.autoRenderChanged.emit(value=value) @property def showRegion(self): return self._showRegion @showRegion.setter def showRegion(self, value): if value != self._showRegion: self._showRegion = value self.showRegionChanged.emit(value=value) @property def alignExtent(self): return self._alignExtent @alignExtent.setter def alignExtent(self, value): if value != self._alignExtent: self._alignExtent = value self.alignExtentChanged.emit(value=value)
class VDigitToolbar(BaseToolbar): """Toolbar for digitization""" def __init__(self, parent, toolSwitcher, MapWindow, digitClass, giface, tools=[]): self.MapWindow = MapWindow self.Map = MapWindow.GetMap() # Map class instance self.tools = tools self.digitClass = digitClass BaseToolbar.__init__(self, parent, toolSwitcher) self.digit = None self._giface = giface self.fType = None # feature type for simple features editing self.editingStarted = Signal("VDigitToolbar.editingStarted") self.editingStopped = Signal("VDigitToolbar.editingStopped") self.editingBgMap = Signal("VDigitToolbar.editingBgMap") self.quitDigitizer = Signal("VDigitToolbar.quitDigitizer") layerTree = self._giface.GetLayerTree() if layerTree: self.editingStarted.connect(layerTree.StartEditing) self.editingStopped.connect(layerTree.StopEditing) self.editingBgMap.connect(layerTree.SetBgMapForEditing) # bind events self.Bind(wx.EVT_SHOW, self.OnShow) # currently selected map layer for editing (reference to MapLayer # instance) self.mapLayer = None # list of vector layers from Layer Manager (only in the current mapset) self.layers = [] self.comboid = self.combo = None self.undo = -1 self.redo = -1 # only one dialog can be open self.settingsDialog = None # create toolbars (two rows optionally) self.InitToolbar(self._toolbarData()) self._default = -1 # default action (digitize new point, line, etc.) self.action = {"desc": "", "type": "", "id": -1} self._currentAreaActionType = None # list of available vector maps self.UpdateListOfLayers(updateTool=True) for tool in ( "addPoint", "addLine", "addBoundary", "addCentroid", "addArea", "addVertex", "deleteLine", "deleteArea", "displayAttr", "displayCats", "editLine", "moveLine", "moveVertex", "removeVertex", "additionalTools", ): if hasattr(self, tool): tool = getattr(self, tool) self.toolSwitcher.AddToolToGroup( group="mouseUse", toolbar=self, tool=tool ) else: Debug.msg(1, "%s skipped" % tool) # custom button for digitization of area/boundary/centroid # TODO: could this be somehow generalized? nAreaTools = 0 if self.tools and "addBoundary" in self.tools: nAreaTools += 1 if self.tools and "addCentroid" in self.tools: nAreaTools += 1 if self.tools and "addArea" in self.tools: nAreaTools += 1 if nAreaTools != 1: self.areaButton = self.CreateSelectionButton( _("Select area/boundary/centroid tool") ) self.areaButtonId = self.InsertControl(5, self.areaButton) self.areaButton.Bind(wx.EVT_BUTTON, self.OnAddAreaMenu) # realize toolbar self.Realize() # workaround for Mac bug. May be fixed by 2.8.8, but not before then. if self.combo: self.combo.Hide() self.combo.Show() # disable undo/redo if self.undo > 0: self.EnableTool(self.undo, False) if self.redo > 0: self.EnableTool(self.redo, False) self.FixSize(width=105) def _toolbarData(self): """Toolbar data""" data = [] self.icons = { "addPoint": MetaIcon( img="point-create", label=_("Digitize new point"), desc=_("Left: new point"), ), "addLine": MetaIcon( img="line-create", label=_("Digitize new line"), desc=_( "Left: new point; Ctrl+Left: undo last point; Right: close line" ), ), "addBoundary": MetaIcon( img="boundary-create", label=_("Digitize new boundary"), desc=_( "Left: new point; Ctrl+Left: undo last point; Right: close line" ), ), "addCentroid": MetaIcon( img="centroid-create", label=_("Digitize new centroid"), desc=_("Left: new point"), ), "addArea": MetaIcon( img="polygon-create", label=_("Digitize new area (boundary without category)"), desc=_("Left: new point"), ), "addVertex": MetaIcon( img="vertex-create", label=_("Add new vertex to line or boundary"), desc=_("Left: Select; Ctrl+Left: Unselect; Right: Confirm"), ), "deleteLine": MetaIcon( img="line-delete", label=_( "Delete selected point(s), line(s), boundary(ies) or centroid(s)" ), desc=_("Left: Select; Ctrl+Left: Unselect; Right: Confirm"), ), "deleteArea": MetaIcon( img="polygon-delete", label=_("Delete selected area(s)"), desc=_("Left: Select; Ctrl+Left: Unselect; Right: Confirm"), ), "displayAttr": MetaIcon( img="attributes-display", label=_("Display/update attributes"), desc=_("Left: Select"), ), "displayCats": MetaIcon( img="cats-display", label=_("Display/update categories"), desc=_("Left: Select"), ), "editLine": MetaIcon( img="line-edit", label=_("Edit selected line/boundary"), desc=_( "Left: new point; Ctrl+Left: undo last point; Right: close line" ), ), "moveLine": MetaIcon( img="line-move", label=_( "Move selected point(s), line(s), boundary(ies) or centroid(s)" ), desc=_("Left: Select; Ctrl+Left: Unselect; Right: Confirm"), ), "moveVertex": MetaIcon( img="vertex-move", label=_("Move selected vertex"), desc=_("Left: Select; Ctrl+Left: Unselect; Right: Confirm"), ), "removeVertex": MetaIcon( img="vertex-delete", label=_("Remove selected vertex"), desc=_("Left: Select; Ctrl+Left: Unselect; Right: Confirm"), ), "settings": BaseIcons["settings"].SetLabel(_("Digitization settings")), "quit": BaseIcons["quit"].SetLabel( label=_("Quit digitizer"), desc=_("Quit digitizer and save changes") ), "help": BaseIcons["help"].SetLabel( label=_("Vector Digitizer manual"), desc=_("Show Vector Digitizer manual"), ), "additionalTools": MetaIcon( img="tools", label=_("Additional tools " "(copy, flip, connect, etc.)"), desc=_("Left: Select; Ctrl+Left: Unselect; Right: Confirm"), ), "undo": MetaIcon( img="undo", label=_("Undo"), desc=_("Undo previous changes") ), "redo": MetaIcon( img="redo", label=_("Redo"), desc=_("Redo previous changes") ), } if not self.tools or "selector" in self.tools: data.append((None,)) if not self.tools or "addPoint" in self.tools: data.append( ("addPoint", self.icons["addPoint"], self.OnAddPoint, wx.ITEM_CHECK) ) if not self.tools or "addLine" in self.tools: data.append( ("addLine", self.icons["addLine"], self.OnAddLine, wx.ITEM_CHECK) ) if not self.tools or "addArea" in self.tools: data.append( ("addArea", self.icons["addArea"], self.OnAddAreaTool, wx.ITEM_CHECK) ) if not self.tools or "deleteLine" in self.tools: data.append( ( "deleteLine", self.icons["deleteLine"], self.OnDeleteLine, wx.ITEM_CHECK, ) ) if not self.tools or "deleteArea" in self.tools: data.append( ( "deleteArea", self.icons["deleteArea"], self.OnDeleteArea, wx.ITEM_CHECK, ) ) if not self.tools or "moveVertex" in self.tools: data.append( ( "moveVertex", self.icons["moveVertex"], self.OnMoveVertex, wx.ITEM_CHECK, ) ) if not self.tools or "addVertex" in self.tools: data.append( ("addVertex", self.icons["addVertex"], self.OnAddVertex, wx.ITEM_CHECK) ) if not self.tools or "removeVertex" in self.tools: data.append( ( "removeVertex", self.icons["removeVertex"], self.OnRemoveVertex, wx.ITEM_CHECK, ) ) if not self.tools or "editLine" in self.tools: data.append( ("editLine", self.icons["editLine"], self.OnEditLine, wx.ITEM_CHECK) ) if not self.tools or "moveLine" in self.tools: data.append( ("moveLine", self.icons["moveLine"], self.OnMoveLine, wx.ITEM_CHECK) ) if not self.tools or "displayCats" in self.tools: data.append( ( "displayCats", self.icons["displayCats"], self.OnDisplayCats, wx.ITEM_CHECK, ) ) if not self.tools or "displayAttr" in self.tools: data.append( ( "displayAttr", self.icons["displayAttr"], self.OnDisplayAttr, wx.ITEM_CHECK, ) ) if not self.tools or "additionalSelf.Tools" in self.tools: data.append( ( "additionalTools", self.icons["additionalTools"], self.OnAdditionalToolMenu, wx.ITEM_CHECK, ) ) if not self.tools or "undo" in self.tools or "redo" in self.tools: data.append((None,)) if not self.tools or "undo" in self.tools: data.append(("undo", self.icons["undo"], self.OnUndo)) if not self.tools or "redo" in self.tools: data.append(("redo", self.icons["redo"], self.OnRedo)) if ( not self.tools or "settings" in self.tools or "help" in self.tools or "quit" in self.tools ): data.append((None,)) if not self.tools or "settings" in self.tools: data.append(("settings", self.icons["settings"], self.OnSettings)) if not self.tools or "help" in self.tools: data.append(("help", self.icons["help"], self.OnHelp)) if not self.tools or "quit" in self.tools: data.append(("quit", self.icons["quit"], self.OnExit)) return self._getToolbarData(data) def OnTool(self, event): """Tool selected -> untoggles previusly selected tool in toolbar""" Debug.msg(3, "VDigitToolbar.OnTool(): id = %s" % event.GetId()) # set cursor self.MapWindow.SetNamedCursor("cross") self.MapWindow.mouse["box"] = "point" self.MapWindow.mouse["use"] = "pointer" aId = self.action.get("id", -1) BaseToolbar.OnTool(self, event) # clear tmp canvas if self.action["id"] != aId or aId == -1: self.MapWindow.polycoords = [] self.MapWindow.ClearLines(pdc=self.MapWindow.pdcTmp) if self.digit and len(self.MapWindow.digit.GetDisplay().GetSelected()) > 0: # cancel action self.MapWindow.OnMiddleDown(None) # set no action if self.action["id"] == -1: self.action = {"desc": "", "type": "", "id": -1} # set focus self.MapWindow.SetFocus() def OnAddPoint(self, event): """Add point to the vector map Laier""" Debug.msg(2, "VDigitToolbar.OnAddPoint()") self.action = {"desc": "addLine", "type": "point", "id": self.addPoint} self.MapWindow.mouse["box"] = "point" def OnAddLine(self, event): """Add line to the vector map layer""" Debug.msg(2, "VDigitToolbar.OnAddLine()") self.action = {"desc": "addLine", "type": "line", "id": self.addLine} self.MapWindow.mouse["box"] = "line" # self.MapWindow.polycoords = [] # reset temp line def OnAddBoundary(self, event): """Add boundary to the vector map layer""" Debug.msg(2, "VDigitToolbar.OnAddBoundary()") self._toggleAreaIfNeeded() # reset temp line if self.action["desc"] != "addLine" or self.action["type"] != "boundary": self.MapWindow.polycoords = [] # update icon and tooltip self.SetToolNormalBitmap(self.addArea, self.icons["addBoundary"].GetBitmap()) self.SetToolShortHelp(self.addArea, self.icons["addBoundary"].GetLabel()) # set action self.action = {"desc": "addLine", "type": "boundary", "id": self.addArea} self.MapWindow.mouse["box"] = "line" self._currentAreaActionType = "boundary" def OnAddCentroid(self, event): """Add centroid to the vector map layer""" Debug.msg(2, "VDigitToolbar.OnAddCentroid()") self._toggleAreaIfNeeded() # update icon and tooltip self.SetToolNormalBitmap(self.addArea, self.icons["addCentroid"].GetBitmap()) self.SetToolShortHelp(self.addArea, self.icons["addCentroid"].GetLabel()) # set action self.action = {"desc": "addLine", "type": "centroid", "id": self.addArea} self.MapWindow.mouse["box"] = "point" self._currentAreaActionType = "centroid" def OnAddArea(self, event): """Add area to the vector map layer""" Debug.msg(2, "VDigitToolbar.OnAddArea()") self._toggleAreaIfNeeded() # update icon and tooltip self.SetToolNormalBitmap(self.addArea, self.icons["addArea"].GetBitmap()) self.SetToolShortHelp(self.addArea, self.icons["addArea"].GetLabel()) # set action self.action = {"desc": "addLine", "type": "area", "id": self.addArea} self.MapWindow.mouse["box"] = "line" self._currentAreaActionType = "area" def _toggleAreaIfNeeded(self): """In some cases, the area tool is not toggled, we have to do it manually.""" if not self.GetToolState(self.addArea): self.ToggleTool(self.addArea, True) self.toolSwitcher.ToolChanged(self.addArea) def OnAddAreaTool(self, event): """Area tool activated.""" Debug.msg(2, "VDigitToolbar.OnAddAreaTool()") # we need the previous id if ( not self._currentAreaActionType or self._currentAreaActionType == "area" ): # default action self.OnAddArea(event) elif self._currentAreaActionType == "boundary": self.OnAddBoundary(event) elif self._currentAreaActionType == "centroid": self.OnAddCentroid(event) def OnAddAreaMenu(self, event): """Digitize area menu (add area/boundary/centroid)""" menuItems = [] if not self.tools or "addArea" in self.tools: menuItems.append((self.icons["addArea"], self.OnAddArea)) if not self.fType and not self.tools or "addBoundary" in self.tools: menuItems.append((self.icons["addBoundary"], self.OnAddBoundary)) if not self.fType and not self.tools or "addCentroid" in self.tools: menuItems.append((self.icons["addCentroid"], self.OnAddCentroid)) self._onMenu(menuItems) def OnExit(self, event=None): """Quit digitization tool""" # stop editing of the currently selected map layer if self.mapLayer: self.StopEditing() # close dialogs if still open if self.settingsDialog: self.settingsDialog.OnCancel(None) # set default mouse settings self.parent.GetMapToolbar().SelectDefault() self.MapWindow.polycoords = [] self.quitDigitizer.emit() def OnMoveVertex(self, event): """Move line vertex""" Debug.msg(2, "Digittoolbar.OnMoveVertex():") self.action = {"desc": "moveVertex", "id": self.moveVertex} self.MapWindow.mouse["box"] = "point" def OnAddVertex(self, event): """Add line vertex""" Debug.msg(2, "Digittoolbar.OnAddVertex():") self.action = {"desc": "addVertex", "id": self.addVertex} self.MapWindow.mouse["box"] = "point" def OnRemoveVertex(self, event): """Remove line vertex""" Debug.msg(2, "Digittoolbar.OnRemoveVertex():") self.action = {"desc": "removeVertex", "id": self.removeVertex} self.MapWindow.mouse["box"] = "point" def OnEditLine(self, event): """Edit line""" Debug.msg(2, "Digittoolbar.OnEditLine():") self.action = {"desc": "editLine", "id": self.editLine} self.MapWindow.mouse["box"] = "line" def OnMoveLine(self, event): """Move line""" Debug.msg(2, "Digittoolbar.OnMoveLine():") self.action = {"desc": "moveLine", "id": self.moveLine} self.MapWindow.mouse["box"] = "box" def OnDeleteLine(self, event): """Delete line""" Debug.msg(2, "Digittoolbar.OnDeleteLine():") self.action = {"desc": "deleteLine", "id": self.deleteLine} self.MapWindow.mouse["box"] = "box" def OnDeleteArea(self, event): """Delete Area""" Debug.msg(2, "Digittoolbar.OnDeleteArea():") self.action = {"desc": "deleteArea", "id": self.deleteArea} self.MapWindow.mouse["box"] = "box" def OnDisplayCats(self, event): """Display/update categories""" Debug.msg(2, "Digittoolbar.OnDisplayCats():") self.action = {"desc": "displayCats", "id": self.displayCats} self.MapWindow.mouse["box"] = "point" def OnDisplayAttr(self, event): """Display/update attributes""" Debug.msg(2, "Digittoolbar.OnDisplayAttr():") self.action = {"desc": "displayAttrs", "id": self.displayAttr} self.MapWindow.mouse["box"] = "point" def OnUndo(self, event): """Undo previous changes""" if self.digit: self.digit.Undo() event.Skip() def OnRedo(self, event): """Undo previous changes""" if self.digit: self.digit.Undo(level=1) event.Skip() def EnableUndo(self, enable=True): """Enable 'Undo' in toolbar :param enable: False for disable """ self._enableTool(self.undo, enable) def EnableRedo(self, enable=True): """Enable 'Redo' in toolbar :param enable: False for disable """ self._enableTool(self.redo, enable) def _enableTool(self, tool, enable): if not self.FindById(tool): return if enable: if self.GetToolEnabled(tool) is False: self.EnableTool(tool, True) else: if self.GetToolEnabled(tool) is True: self.EnableTool(tool, False) def GetAction(self, type="desc"): """Get current action info""" return self.action.get(type, "") def OnSettings(self, event): """Show settings dialog""" if self.digit is None: try: self.digit = self.MapWindow.digit = self.digitClass( mapwindow=self.MapWindow ) except SystemExit: self.digit = self.MapWindow.digit = None if not self.settingsDialog: self.settingsDialog = VDigitSettingsDialog( parent=self.parent, giface=self._giface ) self.settingsDialog.Show() def OnHelp(self, event): """Show digitizer help page in web browser""" self._giface.Help("wxGUI.vdigit") def OnAdditionalToolMenu(self, event): """Menu for additional tools""" point = wx.GetMousePosition() toolMenu = Menu() for label, itype, handler, desc in ( ( _("Break selected lines/boundaries at intersection"), wx.ITEM_CHECK, self.OnBreak, "breakLine", ), ( _("Connect selected lines/boundaries"), wx.ITEM_CHECK, self.OnConnect, "connectLine", ), (_("Copy categories"), wx.ITEM_CHECK, self.OnCopyCats, "copyCats"), ( _("Copy features from (background) vector map"), wx.ITEM_CHECK, self.OnCopy, "copyLine", ), (_("Copy attributes"), wx.ITEM_CHECK, self.OnCopyAttrb, "copyAttrs"), ( _("Feature type conversion"), wx.ITEM_CHECK, self.OnTypeConversion, "typeConv", ), ( _("Flip selected lines/boundaries"), wx.ITEM_CHECK, self.OnFlip, "flipLine", ), ( _("Merge selected lines/boundaries"), wx.ITEM_CHECK, self.OnMerge, "mergeLine", ), ( _("Snap selected lines/boundaries (only to nodes)"), wx.ITEM_CHECK, self.OnSnap, "snapLine", ), (_("Split line/boundary"), wx.ITEM_CHECK, self.OnSplitLine, "splitLine"), (_("Query features"), wx.ITEM_CHECK, self.OnQuery, "queryLine"), ( _("Z bulk-labeling of 3D lines"), wx.ITEM_CHECK, self.OnZBulk, "zbulkLine", ), ): # Add items to the menu item = wx.MenuItem( parentMenu=toolMenu, id=wx.ID_ANY, text=label, kind=itype ) toolMenu.AppendItem(item) self.MapWindow.Bind(wx.EVT_MENU, handler, item) if self.action["desc"] == desc: item.Check(True) # Popup the menu. If an item is selected then its handler # will be called before PopupMenu returns. self.MapWindow.PopupMenu(toolMenu) toolMenu.Destroy() if self.action["desc"] == "addPoint": self.ToggleTool(self.additionalTools, False) def OnCopy(self, event): """Copy selected features from (background) vector map""" if not self.digit: GError(_("No vector map open for editing."), self.parent) return # select background map dlg = VectorDialog( self.parent, title=_("Select background vector map"), layerTree=self._giface.GetLayerTree(), ) if dlg.ShowModal() != wx.ID_OK: dlg.Destroy() return mapName = dlg.GetName(full=True) dlg.Destroy() # close open background map if any bgMap = UserSettings.Get( group="vdigit", key="bgmap", subkey="value", settings_type="internal" ) if bgMap: self.digit.CloseBackgroundMap() self.editingBgMap.emit(mapName=bgMap, unset=True) # open background map for reading UserSettings.Set( group="vdigit", key="bgmap", subkey="value", value=str(mapName), settings_type="internal", ) self.digit.OpenBackgroundMap(mapName) self.editingBgMap.emit(mapName=mapName) if self.action["desc"] == "copyLine": # select previous action self.ToggleTool(self.addPoint, True) self.ToggleTool(self.additionalTools, False) self.OnAddPoint(event) return Debug.msg(2, "Digittoolbar.OnCopy():") self.action = {"desc": "copyLine", "id": self.additionalTools} self.MapWindow.mouse["box"] = "box" def OnSplitLine(self, event): """Split line""" if self.action["desc"] == "splitLine": # select previous action self.ToggleTool(self.addPoint, True) self.ToggleTool(self.additionalTools, False) self.OnAddPoint(event) return Debug.msg(2, "Digittoolbar.OnSplitLine():") self.action = {"desc": "splitLine", "id": self.additionalTools} self.MapWindow.mouse["box"] = "point" def OnCopyCats(self, event): """Copy categories""" if self.action["desc"] == "copyCats": # select previous action self.ToggleTool(self.addPoint, True) self.ToggleTool(self.copyCats, False) self.OnAddPoint(event) return Debug.msg(2, "Digittoolbar.OnCopyCats():") self.action = {"desc": "copyCats", "id": self.additionalTools} self.MapWindow.mouse["box"] = "point" def OnCopyAttrb(self, event): """Copy attributes""" if self.action["desc"] == "copyAttrs": # select previous action self.ToggleTool(self.addPoint, True) self.ToggleTool(self.copyCats, False) self.OnAddPoint(event) return Debug.msg(2, "Digittoolbar.OnCopyAttrb():") self.action = {"desc": "copyAttrs", "id": self.additionalTools} self.MapWindow.mouse["box"] = "point" def OnFlip(self, event): """Flip selected lines/boundaries""" if self.action["desc"] == "flipLine": # select previous action self.ToggleTool(self.addPoint, True) self.ToggleTool(self.additionalTools, False) self.OnAddPoint(event) return Debug.msg(2, "Digittoolbar.OnFlip():") self.action = {"desc": "flipLine", "id": self.additionalTools} self.MapWindow.mouse["box"] = "box" def OnMerge(self, event): """Merge selected lines/boundaries""" if self.action["desc"] == "mergeLine": # select previous action self.ToggleTool(self.addPoint, True) self.ToggleTool(self.additionalTools, False) self.OnAddPoint(event) return Debug.msg(2, "Digittoolbar.OnMerge():") self.action = {"desc": "mergeLine", "id": self.additionalTools} self.MapWindow.mouse["box"] = "box" def OnBreak(self, event): """Break selected lines/boundaries""" if self.action["desc"] == "breakLine": # select previous action self.ToggleTool(self.addPoint, True) self.ToggleTool(self.additionalTools, False) self.OnAddPoint(event) return Debug.msg(2, "Digittoolbar.OnBreak():") self.action = {"desc": "breakLine", "id": self.additionalTools} self.MapWindow.mouse["box"] = "box" def OnSnap(self, event): """Snap selected features""" if self.action["desc"] == "snapLine": # select previous action self.ToggleTool(self.addPoint, True) self.ToggleTool(self.additionalTools, False) self.OnAddPoint(event) return Debug.msg(2, "Digittoolbar.OnSnap():") self.action = {"desc": "snapLine", "id": self.additionalTools} self.MapWindow.mouse["box"] = "box" def OnConnect(self, event): """Connect selected lines/boundaries""" if self.action["desc"] == "connectLine": # select previous action self.ToggleTool(self.addPoint, True) self.ToggleTool(self.additionalTools, False) self.OnAddPoint(event) return Debug.msg(2, "Digittoolbar.OnConnect():") self.action = {"desc": "connectLine", "id": self.additionalTools} self.MapWindow.mouse["box"] = "box" def OnQuery(self, event): """Query selected lines/boundaries""" if self.action["desc"] == "queryLine": # select previous action self.ToggleTool(self.addPoint, True) self.ToggleTool(self.additionalTools, False) self.OnAddPoint(event) return Debug.msg( 2, "Digittoolbar.OnQuery(): %s" % UserSettings.Get(group="vdigit", key="query", subkey="selection"), ) self.action = {"desc": "queryLine", "id": self.additionalTools} self.MapWindow.mouse["box"] = "box" def OnZBulk(self, event): """Z bulk-labeling selected lines/boundaries""" if not self.digit.IsVector3D(): GError( parent=self.parent, message=_("Vector map is not 3D. Operation canceled."), ) return if self.action["desc"] == "zbulkLine": # select previous action self.ToggleTool(self.addPoint, True) self.ToggleTool(self.additionalTools, False) self.OnAddPoint(event) return Debug.msg(2, "Digittoolbar.OnZBulk():") self.action = {"desc": "zbulkLine", "id": self.additionalTools} self.MapWindow.mouse["box"] = "line" def OnTypeConversion(self, event): """Feature type conversion Supported conversions: - point <-> centroid - line <-> boundary """ if self.action["desc"] == "typeConv": # select previous action self.ToggleTool(self.addPoint, True) self.ToggleTool(self.additionalTools, False) self.OnAddPoint(event) return Debug.msg(2, "Digittoolbar.OnTypeConversion():") self.action = {"desc": "typeConv", "id": self.additionalTools} self.MapWindow.mouse["box"] = "box" def OnSelectMap(self, event): """Select vector map layer for editing If there is a vector map layer already edited, this action is firstly terminated. The map layer is closed. After this the selected map layer activated for editing. """ if event.GetSelection() == 0: # create new vector map layer if self.mapLayer: openVectorMap = self.mapLayer.GetName(fullyQualified=False)["name"] else: openVectorMap = None dlg = CreateNewVector( self.parent, exceptMap=openVectorMap, giface=self._giface, cmd=(("v.edit", {"tool": "create"}, "map")), disableAdd=True, ) if dlg and dlg.GetName(): # add layer to map layer tree/map display mapName = dlg.GetName() + "@" + grass.gisenv()["MAPSET"] self._giface.GetLayerList().AddLayer( ltype="vector", name=mapName, checked=True, cmd=["d.vect", "map=%s" % mapName], ) vectLayers = self.UpdateListOfLayers(updateTool=True) selection = vectLayers.index(mapName) # create table ? if dlg.IsChecked("table"): # TODO: replace this by signal # also note that starting of tools such as atm, iclass, # plots etc. should be handled in some better way # than starting randomly from mapdisp and lmgr lmgr = self.parent.GetLayerManager() if lmgr: lmgr.OnShowAttributeTable(None, selection="table") dlg.Destroy() else: self.combo.SetValue(_("Select vector map")) if dlg: dlg.Destroy() return else: selection = event.GetSelection() - 1 # first option is 'New vector map' # skip currently selected map if self.layers[selection] == self.mapLayer: return if self.mapLayer: # deactive map layer for editing self.StopEditing() # select the given map layer for editing self.StartEditing(self.layers[selection]) event.Skip() def StartEditing(self, mapLayer): """Start editing selected vector map layer. :param mapLayer: MapLayer to be edited """ # check if topology is available (skip for hidden - temporary # maps, see iclass for details) if ( not mapLayer.IsHidden() and grass.vector_info(mapLayer.GetName())["level"] != 2 ): dlg = wx.MessageDialog( parent=self.MapWindow, message=_( "Topology for vector map <%s> is not available. " "Topology is required by digitizer.\nDo you want to " "rebuild topology (takes some time) and open the vector map " "for editing?" ) % mapLayer.GetName(), caption=_("Digitizer error"), style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION | wx.CENTRE, ) if dlg.ShowModal() == wx.ID_YES: RunCommand("v.build", map=mapLayer.GetName()) else: return # deactive layer self.Map.ChangeLayerActive(mapLayer, False) # clean map canvas self.MapWindow.EraseMap() # unset background map if needed if mapLayer: if ( UserSettings.Get( group="vdigit", key="bgmap", subkey="value", settings_type="internal", ) == mapLayer.GetName() ): UserSettings.Set( group="vdigit", key="bgmap", subkey="value", value="", settings_type="internal", ) self.parent.SetStatusText( _("Please wait, " "opening vector map <%s> for editing...") % mapLayer.GetName(), 0, ) self.MapWindow.pdcVector = PseudoDC() self.digit = self.MapWindow.digit = self.digitClass(mapwindow=self.MapWindow) self.mapLayer = mapLayer # open vector map (assume that 'hidden' map layer is temporary vector # map) if self.digit.OpenMap(mapLayer.GetName(), tmp=mapLayer.IsHidden()) is None: self.mapLayer = None self.StopEditing() return False # check feature type (only for OGR layers) self.fType = self.digit.GetFeatureType() self.EnableAll() self.EnableUndo(False) self.EnableRedo(False) if self.fType == "point": for tool in ( self.addLine, self.addArea, self.moveVertex, self.addVertex, self.removeVertex, self.editLine, ): self.EnableTool(tool, False) elif self.fType == "linestring": for tool in (self.addPoint, self.addArea): self.EnableTool(tool, False) elif self.fType == "polygon": for tool in (self.addPoint, self.addLine): self.EnableTool(tool, False) elif self.fType: GError( parent=self, message=_( "Unsupported feature type '%(type)s'. Unable to edit " "OGR layer <%(layer)s>." ) % {"type": self.fType, "layer": mapLayer.GetName()}, ) self.digit.CloseMap() self.mapLayer = None self.StopEditing() return False # update toolbar if self.combo: self.combo.SetValue(mapLayer.GetName()) if "map" in self.parent.toolbars: self.parent.toolbars["map"].combo.SetValue(_("Vector digitizer")) # here was dead code to enable vdigit button in toolbar # with if to ignore iclass # some signal (DigitizerStarted) can be emitted here Debug.msg(4, "VDigitToolbar.StartEditing(): layer=%s" % mapLayer.GetName()) # change cursor if self.MapWindow.mouse["use"] == "pointer": self.MapWindow.SetNamedCursor("cross") if not self.MapWindow.resize: self.MapWindow.UpdateMap(render=True) # respect opacity opacity = mapLayer.GetOpacity() if opacity < 1.0: alpha = int(opacity * 255) self.digit.GetDisplay().UpdateSettings(alpha=alpha) # emit signal layerTree = self._giface.GetLayerTree() if layerTree: item = layerTree.FindItemByData("maplayer", self.mapLayer) else: item = None self.editingStarted.emit( vectMap=mapLayer.GetName(), digit=self.digit, layerItem=item ) return True def StopEditing(self): """Stop editing of selected vector map layer. :return: True on success :return: False on failure """ item = None if self.combo: self.combo.SetValue(_("Select vector map")) # save changes if self.mapLayer: Debug.msg( 4, "VDigitToolbar.StopEditing(): layer=%s" % self.mapLayer.GetName() ) if ( UserSettings.Get(group="vdigit", key="saveOnExit", subkey="enabled") is False ): if self.digit.GetUndoLevel() > -1: dlg = wx.MessageDialog( parent=self.parent, message=_("Do you want to save changes " "in vector map <%s>?") % self.mapLayer.GetName(), caption=_("Save changes?"), style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION, ) if dlg.ShowModal() == wx.ID_NO: # revert changes self.digit.Undo(0) dlg.Destroy() self.parent.SetStatusText( _( "Please wait, " "closing and rebuilding topology of " "vector map <%s>..." ) % self.mapLayer.GetName(), 0, ) self.digit.CloseMap() # close open background map if any bgMap = UserSettings.Get( group="vdigit", key="bgmap", subkey="value", settings_type="internal" ) if bgMap: self.digit.CloseBackgroundMap() self.editingBgMap.emit(mapName=bgMap, unset=True) self._giface.GetProgress().SetValue(0) self._giface.WriteCmdLog( _("Editing of vector map <%s> successfully finished") % self.mapLayer.GetName(), notification=Notification.HIGHLIGHT, ) # re-active layer layerTree = self._giface.GetLayerTree() if layerTree: item = layerTree.FindItemByData("maplayer", self.mapLayer) if item and layerTree.IsItemChecked(item): self.Map.ChangeLayerActive(self.mapLayer, True) # change cursor self.MapWindow.SetNamedCursor("default") self.MapWindow.pdcVector = None # close dialogs for dialog in ("attributes", "category"): if self.parent.dialogs[dialog]: self.parent.dialogs[dialog].Close() self.parent.dialogs[dialog] = None self.digit = None self.MapWindow.digit = None self.editingStopped.emit(layerItem=item) self.mapLayer = None self.MapWindow.redrawAll = True return True def UpdateListOfLayers(self, updateTool=False): """Update list of available vector map layers. This list consists only editable layers (in the current mapset) :param updateTool: True to update also toolbar :type updateTool: bool """ Debug.msg(4, "VDigitToolbar.UpdateListOfLayers(): updateTool=%d" % updateTool) layerNameSelected = None # name of currently selected layer if self.mapLayer: layerNameSelected = self.mapLayer.GetName() # select vector map layer in the current mapset layerNameList = [] self.layers = self.Map.GetListOfLayers( ltype="vector", mapset=grass.gisenv()["MAPSET"] ) for layer in self.layers: if layer.name not in layerNameList: # do not duplicate layer layerNameList.append(layer.GetName()) if updateTool: # update toolbar if not self.mapLayer: value = _("Select vector map") else: value = layerNameSelected if not self.comboid: if not self.tools or "selector" in self.tools: self.combo = wx.ComboBox( self, id=wx.ID_ANY, value=value, choices=[ _("New vector map"), ] + layerNameList, size=(80, -1), style=wx.CB_READONLY, ) self.comboid = self.InsertControl(0, self.combo) self.parent.Bind(wx.EVT_COMBOBOX, self.OnSelectMap, self.comboid) else: self.combo.SetItems( [ _("New vector map"), ] + layerNameList ) self.Realize() return layerNameList def GetLayer(self): """Get selected layer for editing -- MapLayer instance""" return self.mapLayer def OnShow(self, event): """Show frame event""" if event.IsShown(): # list of available vector maps self.UpdateListOfLayers(updateTool=True)
class SnappingNodes(wx.EvtHandler): def __init__(self, giface, data, tmp_maps, mapWin): self.giface = giface self.data = data self.tmp_maps = tmp_maps self.mapWin = mapWin wx.EvtHandler.__init__(self) self.snapping = Signal('VNETManager.snapping') # Stores all data related to snapping self.snapData = {} def ComputeNodes(self, activate): """Start/stop snapping mode""" if not haveCtypes: GMessage(parent=self, message=_("Unable to use ctypes. \n") + _("Snapping mode can not be activated.")) return -1 if not activate: if self.tmp_maps.HasTmpVectMap("vnet_snap_points"): self.snapPts.DeleteRenderLayer() self.giface.updateMap.emit(render=False, renderVector=False) if 'cmdThread' in self.snapData: self.snapData['cmdThread'].abort() self.data.SetSnapping(False) self.snapping.emit(evt="deactivated") return -1 self.data.SetSnapping(activate) params, inv_params, flags = self.data.GetParams() if not self.data.InputsErrorMsgs( msg=_("Snapping mode can not be activated."), analysis=None, params=params, inv_params=inv_params, flags=flags, relevant_params=["input", "node_layer"]): return -1 if not self.tmp_maps.HasTmpVectMap("vnet_snap_points"): endStr = _( "Do you really want to activate snapping and overwrite it?") self.snapPts = self.tmp_maps.AddTmpVectMap("vnet_snap_points", endStr) if not self.snapPts: return -1 elif self.snapPts.VectMapState() == 0: dlg = wx.MessageDialog( message=_("Temporary map '%s' was changed outside " + "vector analysis tool.\n" "Do you really want to activate " + "snapping and overwrite it? ") % self.snapPts.GetVectMapName(), caption=_("Overwrite map"), style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION | wx.CENTRE) ret = dlg.ShowModal() dlg.Destroy() if ret == wx.ID_NO: self.tmp_maps.DeleteTmpMap(self.snapPts) return -1 self.data.SetSnapping(True) inpFullName = params["input"] inpName, mapSet = inpFullName.split("@") computeNodes = True if "inputMap" not in self.snapData: pass elif inpFullName != self.snapData["inputMap"].GetVectMapName(): self.snapData["inputMap"] = VectMap(None, inpFullName) elif self.snapData["inputMap"].VectMapState() == 1: computeNodes = False # new map needed if computeNodes: if 'cmdThread' not in self.snapData: self.snapData['cmdThread'] = CmdThread(self) else: self.snapData['cmdThread'].abort() cmd = [ "v.to.points", "input=" + params['input'], "output=" + self.snapPts.GetVectMapName(), "use=node", "--overwrite" ] # process GRASS command with argument self.snapData["inputMap"] = VectMap(None, inpFullName) self.snapData["inputMap"].SaveVectMapState() self.Bind(EVT_CMD_DONE, self._onNodesDone) self.snapData['cmdThread'].RunCmd(cmd) self.snapping.emit(evt="computing_points") return 0 # map is already created and up to date for input data else: self.snapPts.AddRenderLayer() self.giface.updateMap.emit(render=True, renderVector=True) self.snapping.emit(evt="computing_points_done") return 1 def _onNodesDone(self, event): """Update map window, when map with nodes to snap is created""" if not event.aborted: self.snapPts.SaveVectMapState() self.snapPts.AddRenderLayer() self.giface.updateMap.emit(render=True, renderVector=True) self.snapping.emit(evt="computing_points_done")
class SearchModuleWindow(wx.Panel): """Menu tree and search widget for searching modules. Signal: showNotification - attribute 'message' """ def __init__(self, parent, handlerObj, giface, model, id=wx.ID_ANY, **kwargs): self.parent = parent self._handlerObj = handlerObj self._giface = giface self._model = model self.showNotification = Signal("SearchModuleWindow.showNotification") wx.Panel.__init__(self, parent=parent, id=id, **kwargs) # search widget self._search = SearchCtrl(self) self._search.SetDescriptiveText(_("Search")) self._search.ShowCancelButton(True) self._btnAdvancedSearch = Button( self, id=wx.ID_ANY, label=_("Adva&nced search...") ) self._btnAdvancedSearch.SetToolTip( _("Do advanced search using %s tool") % "g.search.module" ) # tree self._tree = CTreeView(model=model, parent=self) self._tree.SetToolTip(_("Double-click to run selected tool")) # buttons self._btnRun = Button(self, id=wx.ID_OK, label=_("&Run...")) self._btnRun.SetToolTip(_("Run selected tool from the tree")) self._btnHelp = Button(self, id=wx.ID_ANY, label=_("H&elp")) self._btnHelp.SetToolTip(_("Show manual for selected tool from the tree")) # bindings self._search.Bind(wx.EVT_TEXT, lambda evt: self.Filter(evt.GetString())) self._search.Bind(wx.EVT_SEARCHCTRL_CANCEL_BTN, lambda evt: self.Filter("")) self._btnRun.Bind(wx.EVT_BUTTON, lambda evt: self.Run()) self._btnHelp.Bind(wx.EVT_BUTTON, lambda evt: self.Help()) self._btnAdvancedSearch.Bind(wx.EVT_BUTTON, lambda evt: self.AdvancedSearch()) self._tree.selectionChanged.connect(self.OnItemSelected) self._tree.itemActivated.connect(lambda node: self.Run(node)) self._layout() self._search.SetFocus() def _layout(self): """Do dialog layout""" sizer = wx.BoxSizer(wx.VERTICAL) # search searchSizer = wx.BoxSizer(wx.HORIZONTAL) searchSizer.Add(self._search, proportion=1, flag=wx.EXPAND | wx.RIGHT, border=5) searchSizer.Add(self._btnAdvancedSearch, proportion=0, flag=wx.EXPAND) sizer.Add(searchSizer, proportion=0, flag=wx.EXPAND | wx.ALL, border=5) # body sizer.Add( self._tree, proportion=1, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=5 ) # buttons btnSizer = wx.BoxSizer(wx.HORIZONTAL) btnSizer.AddStretchSpacer() btnSizer.Add(self._btnHelp, proportion=0, flag=wx.EXPAND | wx.RIGHT, border=5) btnSizer.Add(self._btnRun, proportion=0) sizer.Add(btnSizer, proportion=0, flag=wx.EXPAND | wx.ALL, border=5) self.SetSizerAndFit(sizer) self.SetAutoLayout(True) self.Layout() def Filter(self, text): if text: model = self._model.Filtered( key=["command", "keywords", "description"], value=text ) self._tree.SetModel(model) self._tree.ExpandAll() else: self._tree.SetModel(self._model) def _GetSelectedNode(self): selection = self._tree.GetSelected() if not selection: return None return selection[0] def Run(self, node=None): """Run selected command. :param node: a tree node associated with the module or other item """ if not node: node = self._GetSelectedNode() # nothing selected if not node: return data = node.data # non-leaf nodes if not data: # expand/collapse location/mapset... if self._tree.IsNodeExpanded(node): self._tree.CollapseNode(node, recursive=False) else: self._tree.ExpandNode(node, recursive=False) return # extract name of the handler and create a new call handler = "self._handlerObj." + data["handler"].lstrip("self.") if data["command"]: eval(handler)(event=None, cmd=data["command"].split()) else: eval(handler)(event=None) def Help(self, node=None): """Show documentation for a module""" if not node: node = self._GetSelectedNode() # nothing selected if not node: return data = node.data # non-leaf nodes if not data: return if not data["command"]: # showing nothing for non-modules return # strip parameters from command if present name = data["command"].split()[0] self._giface.Help(name) self.showNotification.emit( message=_("Documentation for %s is now open in the web browser") % name ) def AdvancedSearch(self): """Show advanced search window""" self._handlerObj.RunMenuCmd(cmd=["g.search.modules"]) def OnItemSelected(self, node): """Item selected""" data = node.data if not data or "command" not in data: return if data["command"]: label = data["command"] if data["description"]: label += " -- " + data["description"] else: label = data["description"] self.showNotification.emit(message=label)
class ScatterPlotWidget(wx.Panel, ManageBusyCursorMixin): def __init__(self, parent, scatt_id, scatt_mgr, transpose, id=wx.ID_ANY): # TODO should not be transpose and scatt_id but x, y wx.Panel.__init__(self, parent, id) # bacause of aui (if floatable it can not take cursor from parent) ManageBusyCursorMixin.__init__(self, window=self) self.parent = parent self.full_extend = None self.mode = None self._createWidgets() self._doLayout() self.scatt_id = scatt_id self.scatt_mgr = scatt_mgr self.cidpress = None self.cidrelease = None self.rend_dt = {} self.transpose = transpose self.inverse = False self.SetSize((200, 100)) self.Layout() self.base_scale = 1.2 self.Bind(wx.EVT_CLOSE, lambda event: self.CleanUp()) self.plotClosed = Signal("ScatterPlotWidget.plotClosed") self.cursorMove = Signal("ScatterPlotWidget.cursorMove") self.contex_menu = ScatterPlotContextMenu(plot=self) self.ciddscroll = None self.canvas.mpl_connect('motion_notify_event', self.Motion) self.canvas.mpl_connect('button_press_event', self.OnPress) self.canvas.mpl_connect('button_release_event', self.OnRelease) self.canvas.mpl_connect('draw_event', self.DrawCallback) self.canvas.mpl_connect('figure_leave_event', self.OnCanvasLeave) def DrawCallback(self, event): self.polygon_drawer.DrawCallback(event) self.axes.draw_artist(self.zoom_rect) def _createWidgets(self): # Create the mpl Figure and FigCanvas objects. # 5x4 inches, 100 dots-per-inch # self.dpi = 100 self.fig = Figure((1.0, 1.0), dpi=self.dpi) self.fig.autolayout = True self.canvas = FigCanvas(self, -1, self.fig) self.axes = self.fig.add_axes([0.0, 0.0, 1, 1]) pol = Polygon(list(zip([0], [0])), animated=True) self.axes.add_patch(pol) self.polygon_drawer = PolygonDrawer(self.axes, pol=pol, empty_pol=True) self.zoom_wheel_coords = None self.zoom_rect_coords = None self.zoom_rect = Polygon(list(zip([0], [0])), facecolor='none') self.zoom_rect.set_visible(False) self.axes.add_patch(self.zoom_rect) def ZoomToExtend(self): if self.full_extend: self.axes.axis(self.full_extend) self.canvas.draw() def SetMode(self, mode): self._deactivateMode() if mode == 'zoom': self.ciddscroll = self.canvas.mpl_connect( 'scroll_event', self.ZoomWheel) self.mode = 'zoom' elif mode == 'zoom_extend': self.mode = 'zoom_extend' elif mode == 'pan': self.mode = 'pan' elif mode: self.polygon_drawer.SetMode(mode) def SetSelectionPolygonMode(self, activate): self.polygon_drawer.SetSelectionPolygonMode(activate) def _deactivateMode(self): self.mode = None self.polygon_drawer.SetMode(None) if self.ciddscroll: self.canvas.mpl_disconnect(self.ciddscroll) self.zoom_rect.set_visible(False) self._stopCategoryEdit() def GetCoords(self): coords = self.polygon_drawer.GetCoords() if coords is None: return if self.transpose: for c in coords: tmp = c[0] c[0] = c[1] c[1] = tmp return coords def SetEmpty(self): return self.polygon_drawer.SetEmpty() def OnRelease(self, event): if not self.mode == "zoom": return self.zoom_rect.set_visible(False) self.ZoomRectangle(event) self.canvas.draw() def OnPress(self, event): 'on button press we will see if the mouse is over us and store some data' if not event.inaxes: return if self.mode == "zoom_extend": self.ZoomToExtend() if event.xdata and event.ydata: self.zoom_wheel_coords = {'x': event.xdata, 'y': event.ydata} self.zoom_rect_coords = {'x': event.xdata, 'y': event.ydata} else: self.zoom_wheel_coords = None self.zoom_rect_coords = None def _stopCategoryEdit(self): 'disconnect all the stored connection ids' if self.cidpress: self.canvas.mpl_disconnect(self.cidpress) if self.cidrelease: self.canvas.mpl_disconnect(self.cidrelease) # self.canvas.mpl_disconnect(self.cidmotion) def _doLayout(self): self.main_sizer = wx.BoxSizer(wx.VERTICAL) self.main_sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW) self.SetSizer(self.main_sizer) self.main_sizer.Fit(self) def Plot(self, cats_order, scatts, ellipses, styles): """Redraws the figure """ callafter_list = [] if self.full_extend: cx = self.axes.get_xlim() cy = self.axes.get_ylim() c = cx + cy else: c = None q = Queue() _rendDtMemmapsToFiles(self.rend_dt) p = Process(target=MergeImg, args=(cats_order, scatts, styles, self.rend_dt, q)) p.start() merged_img, self.full_extend, self.rend_dt = q.get() p.join() _rendDtFilesToMemmaps(self.rend_dt) merged_img = np.memmap( filename=merged_img['dt'], shape=merged_img['sh']) #merged_img, self.full_extend = MergeImg(cats_order, scatts, styles, None) self.axes.clear() self.axes.axis('equal') if self.transpose: merged_img = np.transpose(merged_img, (1, 0, 2)) img = imshow(self.axes, merged_img, extent=[int(ceil(x)) for x in self.full_extend], origin='lower', interpolation='nearest', aspect="equal") callafter_list.append([self.axes.draw_artist, [img]]) callafter_list.append([grass.try_remove, [merged_img.filename]]) for cat_id in cats_order: if cat_id == 0: continue if cat_id not in ellipses: continue e = ellipses[cat_id] if not e: continue colors = styles[cat_id]['color'].split(":") if self.transpose: e['theta'] = 360 - e['theta'] + 90 if e['theta'] >= 360: e['theta'] = abs(360 - e['theta']) e['pos'] = [e['pos'][1], e['pos'][0]] ellip = Ellipse(xy=e['pos'], width=e['width'], height=e['height'], angle=e['theta'], edgecolor="w", linewidth=1.5, facecolor='None') self.axes.add_artist(ellip) callafter_list.append([self.axes.draw_artist, [ellip]]) color = map( lambda v: int(v) / 255.0, styles[cat_id]['color'].split(":")) ellip = Ellipse(xy=e['pos'], width=e['width'], height=e['height'], angle=e['theta'], edgecolor=color, linewidth=1, facecolor='None') self.axes.add_artist(ellip) callafter_list.append([self.axes.draw_artist, [ellip]]) center = Line2D([e['pos'][0]], [e['pos'][1]], marker='x', markeredgecolor='w', # markerfacecolor=color, markersize=2) self.axes.add_artist(center) callafter_list.append([self.axes.draw_artist, [center]]) callafter_list.append([self.fig.canvas.blit, []]) if c: self.axes.axis(c) wx.CallAfter(lambda: self.CallAfter(callafter_list)) def CallAfter(self, funcs_list): while funcs_list: fcn, args = funcs_list.pop(0) fcn(*args) self.canvas.draw() def CleanUp(self): self.plotClosed.emit(scatt_id=self.scatt_id) self.Destroy() def ZoomWheel(self, event): # get the current x and y limits if not event.inaxes: return # tcaswell # http://stackoverflow.com/questions/11551049/matplotlib-plot-zooming-with-scroll-wheel cur_xlim = self.axes.get_xlim() cur_ylim = self.axes.get_ylim() xdata = event.xdata ydata = event.ydata if event.button == 'up': scale_factor = 1 / self.base_scale elif event.button == 'down': scale_factor = self.base_scale else: scale_factor = 1 extend = (xdata - (xdata - cur_xlim[0]) * scale_factor, xdata + (cur_xlim[1] - xdata) * scale_factor, ydata - (ydata - cur_ylim[0]) * scale_factor, ydata + (cur_ylim[1] - ydata) * scale_factor) self.axes.axis(extend) self.canvas.draw() def ZoomRectangle(self, event): # get the current x and y limits if not self.mode == "zoom": return if event.inaxes is None: return if event.button != 1: return cur_xlim = self.axes.get_xlim() cur_ylim = self.axes.get_ylim() x1, y1 = event.xdata, event.ydata x2 = deepcopy(self.zoom_rect_coords['x']) y2 = deepcopy(self.zoom_rect_coords['y']) if x1 == x2 or y1 == y2: return if x1 > x2: tmp = x1 x1 = x2 x2 = tmp if y1 > y2: tmp = y1 y1 = y2 y2 = tmp self.axes.axis((x1, x2, y1, y2)) # self.axes.set_xlim(x1, x2)#, auto = True) # self.axes.set_ylim(y1, y2)#, auto = True) self.canvas.draw() def Motion(self, event): self.PanMotion(event) self.ZoomRectMotion(event) if event.inaxes is None: return self.cursorMove.emit( x=event.xdata, y=event.ydata, scatt_id=self.scatt_id) def OnCanvasLeave(self, event): self.cursorMove.emit(x=None, y=None, scatt_id=self.scatt_id) def PanMotion(self, event): 'on mouse movement' if not self.mode == "pan": return if event.inaxes is None: return if event.button != 1: return cur_xlim = self.axes.get_xlim() cur_ylim = self.axes.get_ylim() x, y = event.xdata, event.ydata mx = (x - self.zoom_wheel_coords['x']) * 0.6 my = (y - self.zoom_wheel_coords['y']) * 0.6 extend = ( cur_xlim[0] - mx, cur_xlim[1] - mx, cur_ylim[0] - my, cur_ylim[1] - my) self.zoom_wheel_coords['x'] = x self.zoom_wheel_coords['y'] = y self.axes.axis(extend) # self.canvas.copy_from_bbox(self.axes.bbox) # self.canvas.restore_region(self.background) self.canvas.draw() def ZoomRectMotion(self, event): if not self.mode == "zoom": return if event.inaxes is None: return if event.button != 1: return x1, y1 = event.xdata, event.ydata self.zoom_rect.set_visible(True) x2 = self.zoom_rect_coords['x'] y2 = self.zoom_rect_coords['y'] self.zoom_rect.xy = ((x1, y1), (x1, y2), (x2, y2), (x2, y1), (x1, y1)) # self.axes.draw_artist(self.zoom_rect) self.canvas.draw()
class RecentFilesMenu: """Add recent files history menu Signal FileRequested is emitted if you request file from recent files menu :param str path: file path you requested :param bool file_exists: file path exists or not :param obj file_history: file history obj instance :param str app_name: required for group name of recent files path written into the .recent_files file :param obj parent_menu: menu widget instance where be inserted recent files menu on the specified position :param int pos: position (index) where insert recent files menu in the parent menu :param int history_len: the maximum number of file paths written into the .recent_files file to app name group """ recent_files = ".recent_files" def __init__(self, app_name, parent_menu, pos, history_len=10): self._history_len = history_len self._parent_menu = parent_menu self._pos = pos self.file_requested = Signal("RecentFilesMenu.FileRequested") self._filehistory = wx.FileHistory(maxFiles=history_len) # Recent files path stored in GRASS GIS config dir in the # .recent_files file in the group by application name self._config = wx.FileConfig( style=wx.CONFIG_USE_LOCAL_FILE, localFilename=os.path.join( utils.GetSettingsPath(), self.recent_files, ), ) self._config.SetPath(strPath=app_name) self._filehistory.Load(self._config) self.RemoveNonExistentFiles() self.recent = wx.Menu() self._filehistory.UseMenu(self.recent) self._filehistory.AddFilesToMenu() # Show recent files menu if count of items in menu > 0 if self._filehistory.GetCount() > 0: self._insertMenu() def _insertMenu(self): """Insert recent files menu into the parent menu on the specified position if count of menu items > 0""" self._parent_menu.Insert( pos=self._pos, id=wx.ID_ANY, text=_("&Recent Files"), submenu=self.recent, ) self.recent.Bind( wx.EVT_MENU_RANGE, self._onFileHistory, id=wx.ID_FILE1, id2=wx.ID_FILE + self._history_len, ) def _onFileHistory(self, event): """Choose recent file from menu event""" file_exists = True file_index = event.GetId() - wx.ID_FILE1 path = self._filehistory.GetHistoryFile(file_index) if not os.path.exists(path): self.RemoveFileFromHistory(file_index) file_exists = False self.file_requested.emit( path=path, file_exists=file_exists, file_history=self._filehistory, ) def AddFileToHistory(self, filename): """Add file to history, and save history into '.recent_files' file :param str filename: file path :return None """ if self._filehistory.GetCount() == 0: self._insertMenu() if filename: self._filehistory.AddFileToHistory(filename) self._filehistory.Save(self._config) self._config.Flush() def RemoveFileFromHistory(self, file_index): """Remove file from the history. :param int file_index: filed index :return: None """ self._filehistory.RemoveFileFromHistory(i=file_index) self._filehistory.Save(self._config) self._config.Flush() def RemoveNonExistentFiles(self): """Remove non existent files from the history""" for i in reversed(range(0, self._filehistory.GetCount())): file = self._filehistory.GetHistoryFile(index=i) if not os.path.exists(file): self._filehistory.RemoveFileFromHistory(i=i) self._filehistory.Save(self._config) self._config.Flush()
class RenderWMSMgr(wx.EvtHandler): """Fetch and prepare WMS data for rendering. """ def __init__(self, layer, env): if not haveGdal: sys.stderr.write(_("Unable to load GDAL Python bindings.\n" "WMS layers can not be displayed without the bindings.\n")) self.layer = layer wx.EvtHandler.__init__(self) # thread for d.wms commands self.thread = gThread() self._startTime = None self.downloading = False self.renderedRegion = None self.updateMap = True self.fetched_data_cmd = None self.tempMap = grass.tempfile() self.dstSize = {} self.dataFetched = Signal('RenderWMSMgr.dataFetched') self.updateProgress = Signal('RenderWMSMgr.updateProgress') self.renderingFailed = Signal('RenderWMSMgr.renderingFailed') def __del__(self): try_remove(self.tempMap) def Render(self, cmd, env): """If it is needed, download missing WMS data. .. todo:: lmgr deletes mapfile and maskfile when order of layers was changed (drag and drop) - if deleted, fetch data again """ if not haveGdal: return Debug.msg(1, "RenderWMSMgr.Render(%s): force=%d img=%s" % (self.layer, self.layer.forceRender, self.layer.mapfile)) env = copy.copy(env) self.dstSize['cols'] = int(env["GRASS_RENDER_WIDTH"]) self.dstSize['rows'] = int(env["GRASS_RENDER_HEIGHT"]) region = self._getRegionDict(env) self._fitAspect(region, self.dstSize) self.updateMap = True fetchData = True # changed to True when calling Render() zoomChanged = False if self.renderedRegion is None or \ cmd != self.fetched_data_cmd: fetchData = True else: for c in ['north', 'south', 'east', 'west']: if self.renderedRegion and \ region[c] != self.renderedRegion[c]: fetchData = True break for c in ['e-w resol', 'n-s resol']: if self.renderedRegion and \ region[c] != self.renderedRegion[c]: zoomChanged = True break if fetchData: self.fetched_data_cmd = None self.renderedRegion = region try_remove(self.layer.mapfile) try_remove(self.tempMap) self.currentPid = self.thread.GetId() # self.thread.Terminate() self.downloading = True self.fetching_cmd = cmd env["GRASS_RENDER_FILE"] = self.tempMap env["GRASS_REGION"] = self._createRegionStr(region) cmd_render = copy.deepcopy(cmd) cmd_render[1]['quiet'] = True # be quiet self._startTime = time.time() self.thread.Run(callable=self._render, cmd=cmd_render, env=env, ondone=self.OnRenderDone) self.layer.forceRender = False self.updateProgress.emit(layer=self.layer) def _render(self, cmd, env): try: return grass.run_command(cmd[0], env=env, **cmd[1]) except CalledModuleError as e: grass.error(e) return 1 def OnRenderDone(self, event): """Fetch data """ if event.pid != self.currentPid: return self.downloading = False if not self.updateMap: self.updateProgress.emit(layer=self.layer) self.renderedRegion = None self.fetched_data_cmd = None return self.mapMerger = GDALRasterMerger( targetFile=self.layer.mapfile, region=self.renderedRegion, bandsNum=3, gdalDriver='PNM', fillValue=0) self.mapMerger.AddRasterBands(self.tempMap, {1: 1, 2: 2, 3: 3}) del self.mapMerger self.maskMerger = GDALRasterMerger( targetFile=self.layer.maskfile, region=self.renderedRegion, bandsNum=1, gdalDriver='PNM', fillValue=0) #{4 : 1} alpha channel (4) to first and only channel (1) in mask self.maskMerger.AddRasterBands(self.tempMap, {4: 1}) del self.maskMerger self.fetched_data_cmd = self.fetching_cmd Debug.msg(1, "RenderWMSMgr.OnRenderDone(%s): ret=%d time=%f" % (self.layer, event.ret, time.time() - self._startTime)) self.dataFetched.emit(layer=self.layer) def _getRegionDict(self, env): """Parse string from GRASS_REGION env variable into dict. """ region = {} parsedRegion = env["GRASS_REGION"].split(';') for r in parsedRegion: r = r.split(':') r[0] = r[0].strip() if len(r) < 2: continue try: if r[0] in ['cols', 'rows']: region[r[0]] = int(r[1]) else: region[r[0]] = float(r[1]) except ValueError: region[r[0]] = r[1] return region def _createRegionStr(self, region): """Create string for GRASS_REGION env variable from dict created by _getRegionDict. """ regionStr = '' for k, v in region.iteritems(): item = k + ': ' + str(v) if regionStr: regionStr += '; ' regionStr += item return regionStr def IsDownloading(self): """Is it downloading any data from WMS server? """ return self.downloading def _fitAspect(self, region, size): """Compute region parameters to have direction independent resolution. """ if region['n-s resol'] > region['e-w resol']: delta = region['n-s resol'] * size['cols'] / 2 center = (region['east'] - region['west']) / 2 region['east'] = center + delta + region['west'] region['west'] = center - delta + region['west'] region['e-w resol'] = region['n-s resol'] else: delta = region['e-w resol'] * size['rows'] / 2 center = (region['north'] - region['south']) / 2 region['north'] = center + delta + region['south'] region['south'] = center - delta + region['south'] region['n-s resol'] = region['e-w resol'] def Abort(self): """Abort rendering process""" Debug.msg(1, "RenderWMSMgr({}).Abort()".format(self.layer)) self.thread.Terminate() # force rendering layer next time self.layer.forceRender = True self.updateMap = False self.thread.Terminate(False)
class ScattsManager: """Main controller """ def __init__(self, guiparent, giface, iclass_mapwin=None): self.giface = giface self.mapDisp = giface.GetMapDisplay() if iclass_mapwin: self.mapWin = iclass_mapwin else: self.mapWin = giface.GetMapWindow() self.guiparent = guiparent self.show_add_scatt_plot = False self.core = Core() self.cats_mgr = CategoriesManager(self, self.core) self.render_mgr = PlotsRenderingManager(scatt_mgr=self, cats_mgr=self.cats_mgr, core=self.core) self.thread = gThread() self.plots = {} self.plot_mode = None self.pol_sel_mode = [False, None] self.data_set = False self.cursorPlotMove = Signal("ScattsManager.cursorPlotMove") self.renderingStarted = self.render_mgr.renderingStarted self.renderingFinished = self.render_mgr.renderingFinished self.computingStarted = Signal("ScattsManager.computingStarted") if iclass_mapwin: self.digit_conn = IClassDigitConnection(self, self.mapWin, self.core.CatRastUpdater()) self.iclass_conn = IClassConnection(self, iclass_mapwin.parent, self.cats_mgr) else: self.digit_conn = IMapWinDigitConnection() self.iclass_conn = IMapDispConnection(scatt_mgr=self, cats_mgr=self.cats_mgr, giface=self.giface) self._initSettings() self.modeSet = Signal("ScattsManager.mondeSet") def CleanUp(self): self.thread.Terminate() # there should be better way hot to clean up the thread # than calling the clean up function outside the thread, # which still may running self.core.CleanUp() def CleanUpDone(self): for scatt_id, scatt in self.plots.items(): if scatt['scatt']: scatt['scatt'].CleanUp() self.plots.clear() def _initSettings(self): """Initialization of settings (if not already defined) """ # initializes default settings initSettings = [ ['selection', 'sel_pol', (255, 255, 0)], ['selection', 'sel_pol_vertex', (255, 0, 0)], ['selection', 'sel_area', (0, 255, 19)], ['selection', "snap_tresh", 10], ['selection', 'sel_area_opacty', 50], ['ellipses', 'show_ellips', True], ] for init in initSettings: UserSettings.ReadSettingsFile() UserSettings.Append(dict=UserSettings.userSettings, group='scatt', key=init[0], subkey=init[1], value=init[2], overwrite=False) def SetData(self): self.iclass_conn.SetData() self.digit_conn.SetData() def SetBands(self, bands): self.busy = wx.BusyInfo(_("Loading data...")) self.data_set = False self.thread.Run(callable=self.core.CleanUp, ondone=lambda event: self.CleanUpDone()) if self.show_add_scatt_plot: show_add = True else: show_add = False self.all_bands_to_bands = dict(zip(bands, [-1] * len(bands))) self.all_bands = bands self.region = GetRegion() ncells = self.region["rows"] * self.region["cols"] if ncells > MAX_NCELLS: del self.busy self.data_set = True return self.bands = bands[:] self.bands_info = {} valid_bands = [] for b in self.bands[:]: i = GetRasterInfo(b) self.bands_info[b] = i if i is not None: valid_bands.append(b) for i, b in enumerate(valid_bands): # name : index in core bands - # if not in core bands (not CELL type) -> index = -1 self.all_bands_to_bands[b] = i self.thread.Run(callable=self.core.SetData, bands=valid_bands, ondone=self.SetDataDone, userdata={"show_add": show_add}) def SetDataDone(self, event): del self.busy self.data_set = True todo = event.ret self.bad_bands = event.ret bands = self.core.GetBands() self.bad_rasts = event.ret self.cats_mgr.SetData() if event.userdata['show_add']: self.AddScattPlot() def GetBands(self): return self.core.GetBands() def AddScattPlot(self): if not self.data_set and self.iclass_conn: self.show_add_scatt_plot = True self.iclass_conn.SetData() self.show_add_scatt_plot = False return if not self.data_set: GError(_('No data set.')) return self.computingStarted.emit() bands = self.core.GetBands() #added_bands_ids = [] # for scatt_id in self.plots): # added_bands_ids.append[idBandsToidScatt(scatt_id)] self.digit_conn.Update() ncells = self.region["rows"] * self.region["cols"] if ncells > MAX_NCELLS: GError( _( parent=self.guiparent, mmessage=_( "Interactive Scatter Plot Tool can not be used.\n" "Number of cells (rows*cols) <%d> in current region" "is higher than maximum limit <%d>.\n\n" "You can reduce number of cells in current region using <g.region> command." % (ncells, MAX_NCELLS)))) return elif ncells > WARN_NCELLS: dlg = wx.MessageDialog( parent=self.guiparent, message=_("Number of cells (rows*cols) <%d> in current region is " "higher than recommended threshold <%d>.\n" "It is strongly advised to reduce number of cells " "in current region below recommend threshold.\n " "It can be done by <g.region> command.\n\n" "Do you want to continue using " "Interactive Scatter Plot Tool with this region?" % (ncells, WARN_NCELLS)), style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_WARNING) ret = dlg.ShowModal() if ret != wx.ID_YES: return dlg = AddScattPlotDialog(parent=self.guiparent, bands=self.all_bands, check_bands_callback=self.CheckBands) if dlg.ShowModal() == wx.ID_OK: scatt_ids = [] sel_bands = dlg.GetBands() for b_1, b_2 in sel_bands: transpose = False if b_1 > b_2: transpose = True tmp_band = b_2 b_2 = b_1 b_1 = tmp_band b_1_id = self.all_bands_to_bands[self.all_bands[b_1]] b_2_id = self.all_bands_to_bands[self.all_bands[b_2]] scatt_id = idBandsToidScatt(b_1_id, b_2_id, len(bands)) if scatt_id in self.plots: continue self.plots[scatt_id] = {'transpose': transpose, 'scatt': None} scatt_ids.append(scatt_id) self._addScattPlot(scatt_ids) dlg.Destroy() def CheckBands(self, b_1, b_2): bands = self.core.GetBands() added_scatts_ids = self.plots.keys() b_1_id = self.all_bands_to_bands[self.all_bands[b_1]] b_2_id = self.all_bands_to_bands[self.all_bands[b_1]] scatt_id = idBandsToidScatt(b_1_id, b_2_id, len(bands)) if scatt_id in added_scatts_ids: GWarning( parent=self.guiparent, message=_( "Scatter plot with same band combination (regardless x y order) " "is already displayed.")) return False b_1_name = self.all_bands[b_1] b_2_name = self.all_bands[b_2] b_1_i = self.bands_info[b_1_name] b_2_i = self.bands_info[b_2_name] err = "" for b in [b_1_name, b_2_name]: if self.bands_info[b] is None: err += _("Band <%s> is not CELL (integer) type.\n" % b) if err: GMessage(parent=self.guiparent, message=_("Scatter plot cannot be added.\n" + err)) return False mrange = b_1_i['range'] * b_2_i['range'] if mrange > MAX_SCATT_SIZE: GWarning(parent=self.guiparent, message=_("Scatter plot cannot be added.\n" "Multiple of bands ranges <%s:%d * %s:%d = %d> " "is higher than maximum limit <%d>.\n" % (b_1_name, b_1_i['range'], b_1_name, b_2_i['range'], mrange, MAX_SCATT_SIZE))) return False elif mrange > WARN_SCATT_SIZE: dlg = wx.MessageDialog( parent=self.guiparent, message=_( "Multiple of bands ranges <%s:%d * %s:%d = %d> " "is higher than recommended limit <%d>.\n" "It is strongly advised to reduce range extend of bands" "(e. g. using r.rescale) below recommended threshold.\n\n" "Do you really want to add this scatter plot?" % (b_1_name, b_1_i['range'], b_1_name, b_2_i['range'], mrange, WARN_SCATT_SIZE)), style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_WARNING) ret = dlg.ShowModal() if ret != wx.ID_YES: return False return True def _addScattPlot(self, scatt_ids): self.render_mgr.NewRunningProcess() self.thread.Run(callable=self.core.AddScattPlots, scatt_ids=scatt_ids, ondone=self.AddScattPlotDone) def AddScattPlotDone(self, event): if not self.data_set: return scatt_ids = event.kwds['scatt_ids'] for s_id in scatt_ids: trans = self.plots[s_id]['transpose'] self.plots[s_id]['scatt'] = self.guiparent.NewScatterPlot( scatt_id=s_id, transpose=trans) self.plots[s_id]['scatt'].plotClosed.connect(self.PlotClosed) self.plots[s_id]['scatt'].cursorMove.connect( lambda x, y, scatt_id: self.cursorPlotMove.emit(x=x, y=y, scatt_id=scatt_id)) if self.plot_mode: self.plots[s_id]['scatt'].SetMode(self.plot_mode) self.plots[s_id]['scatt'].ZoomToExtend() self.render_mgr.RunningProcessDone() def PlotClosed(self, scatt_id): del self.plots[scatt_id] def SetPlotsMode(self, mode): self.plot_mode = mode for scatt in self.plots.itervalues(): if scatt['scatt']: scatt['scatt'].SetMode(mode) self.modeSet.emit(mode=mode) def ActivateSelectionPolygonMode(self, activate): self.pol_sel_mode[0] = activate for scatt in self.plots.itervalues(): if not scatt['scatt']: continue scatt['scatt'].SetSelectionPolygonMode(activate) if not activate and self.plot_mode not in [ 'zoom', 'pan', 'zoom_extend']: self.SetPlotsMode(None) self.render_mgr.RunningProcessDone() return activate def ProcessSelectionPolygons(self, process_mode): scatts_polygons = {} for scatt_id, scatt in self.plots.iteritems(): if not scatt['scatt']: continue coords = scatt['scatt'].GetCoords() if coords is not None: scatts_polygons[scatt_id] = coords if not scatts_polygons: return value = 1 if process_mode == 'remove': value = 0 sel_cat_id = self.cats_mgr.GetSelectedCat() if not sel_cat_id: dlg = wx.MessageDialog( parent=self.guiparent, message=_( "In order to select arrea in scatter plot, " "you have to select class first.\n\n" "There is no class yet, " "do you want to create one?"), caption=_("No class selected"), style=wx.YES_NO) if dlg.ShowModal() == wx.ID_YES: self.iclass_conn.EmptyCategories() sel_cat_id = self.cats_mgr.GetSelectedCat() if not sel_cat_id: return for scatt in self.plots.itervalues(): if scatt['scatt']: scatt['scatt'].SetEmpty() self.computingStarted.emit() self.render_mgr.NewRunningProcess() self.render_mgr.CategoryChanged(cat_ids=[sel_cat_id]) self.render_mgr.CategoryCondsChanged(cat_ids=[sel_cat_id]) self.thread.Run(callable=self.core.UpdateCategoryWithPolygons, cat_id=sel_cat_id, scatts_pols=scatts_polygons, value=value, ondone=self.SetEditCatDataDone) def SetEditCatDataDone(self, event): if not self.data_set: return self.render_mgr.RunningProcessDone() if event.exception: GError( _("Error occurred during computation of scatter plot category:\n%s"), parent=self.guiparent, showTraceback=False) cat_id = event.ret self.iclass_conn.RenderCatRast(cat_id) def SettingsUpdated(self, chanaged_setts): self.render_mgr.RenderRequest() #['ellipses', 'show_ellips'] def GetCategoriesManager(self): return self.cats_mgr
class VNETAnalysisParameters: def __init__(self, an_props): self.an_props = an_props self.params = { "analysis": self.an_props.used_an[0], "input": "", "arc_layer": "", "node_layer": "", "arc_column": "", "arc_backward_column": "", "node_column": "", "turn_layer": "", "turn_cat_layer": "", "iso_lines": "", # TODO check validity "max_dist": 0, } # TODO check validity self.flags = {"t": False} self.parametersChanged = Signal("VNETAnalysisParameters.parametersChanged") def SetParams(self, params, flags): changed_params = {} for p, v in six.iteritems(params): if p == "analysis" and v not in self.an_props.used_an: continue if p == "input": mapName, mapSet = ParseMapStr(v) v = mapName + "@" + mapSet if p in self.params: if isinstance(v, str): v = v.strip() self.params[p] = v changed_params[p] = v changed_flags = {} for p, v in six.iteritems(flags): if p in self.flags: self.flags[p] = v changed_flags[p] = v self.parametersChanged.emit( method="SetParams", kwargs={"changed_params": changed_params, "changed_flags": changed_flags}, ) return changed_params, changed_flags def GetParam(self, param): invParams = [] if param in [ "input", "arc_layer", "node_layer", "arc_column", "arc_backward_column", "node_column", "turn_layer", "turn_cat_layer", ]: invParams = self._getInvalidParams(self.params) if invParams: return self.params[param], False return self.params[param], True def GetParams(self): invParams = self._getInvalidParams(self.params) return self.params, invParams, self.flags def _getInvalidParams(self, params): """Check of analysis input data for invalid values (Parameters tab)""" # dict of invalid values {key from self.itemData (comboboxes from # Parameters tab) : invalid value} invParams = [] # check vector map if params["input"]: mapName, mapSet = params["input"].split("@") if mapSet in grass.list_grouped("vector"): vectMaps = grass.list_grouped("vector")[mapSet] if not params["input"] or mapName not in vectMaps: invParams = list(params.keys())[:] return invParams # check arc/node layer layers = utils.GetVectorNumberOfLayers(params["input"]) for layer in ["arc_layer", "node_layer", "turn_layer", "turn_cat_layer"]: if not layers or params[layer] not in layers: invParams.append(layer) dbInfo = VectorDBInfo(params["input"]) try: table = dbInfo.GetTable(int(params["arc_layer"])) columnchoices = dbInfo.GetTableDesc(table) except (KeyError, ValueError): table = None # check costs columns for col in ["arc_column", "arc_backward_column", "node_column"]: if col == "node_column": try: table = dbInfo.GetTable(int(params["node_layer"])) columnchoices = dbInfo.GetTableDesc(table) except (KeyError, ValueError): table = None if not table or not params[col] in list(columnchoices.keys()): invParams.append(col) continue if columnchoices[params[col]]["type"] not in [ "integer", "double precision", ]: invParams.append(col) continue return invParams
class CategoriesManager: """Manages categories list of scatter plot. """ def __init__(self, scatt_mgr, core): self.core = core self.scatt_mgr = scatt_mgr self.cats = {} self.cats_ids = [] self.sel_cat_id = None self.exportRaster = None self.initialized = Signal('CategoriesManager.initialized') self.setCategoryAttrs = Signal('CategoriesManager.setCategoryAttrs') self.deletedCategory = Signal('CategoriesManager.deletedCategory') self.addedCategory = Signal('CategoriesManager.addedCategory') def ChangePosition(self, cat_id, new_pos): if new_pos >= len(self.cats_ids): return False try: pos = self.cats_ids.index(cat_id) except: return False if pos > new_pos: pos -= 1 self.cats_ids.remove(cat_id) self.cats_ids.insert(new_pos, cat_id) self.scatt_mgr.render_mgr.RenderRequest() return True def _addCategory(self, cat_id): self.scatt_mgr.thread.Run(callable=self.core.AddCategory, cat_id=cat_id) def SetData(self): if not self.scatt_mgr.data_set: return for cat_id in self.cats_ids: self.scatt_mgr.thread.Run(callable=self.core.AddCategory, cat_id=cat_id) def AddCategory(self, cat_id=None, name=None, color=None, nstd=None): if cat_id is None: if self.cats_ids: cat_id = max(self.cats_ids) + 1 else: cat_id = 1 if self.scatt_mgr.data_set: self.scatt_mgr.thread.Run(callable=self.core.AddCategory, cat_id=cat_id) # TODO check number of cats # if ret < 0: #TODO # return -1; self.cats[cat_id] = { 'name': 'class_%d' % cat_id, 'color': "0:0:0", 'opacity': 1.0, 'show': True, 'nstd': 1.0, } self.cats_ids.insert(0, cat_id) if name is not None: self.cats[cat_id]["name"] = name if color is not None: self.cats[cat_id]["color"] = color if nstd is not None: self.cats[cat_id]["nstd"] = nstd self.addedCategory.emit(cat_id=cat_id, name=self.cats[cat_id]["name"], color=self.cats[cat_id]["color"]) return cat_id def SetCategoryAttrs(self, cat_id, attrs_dict): render = False update_cat_rast = [] for k, v in attrs_dict.iteritems(): if not render and k in ['color', 'opacity', 'show', 'nstd']: render = True if k in ['color', 'name']: update_cat_rast.append(k) self.cats[cat_id][k] = v if render: self.scatt_mgr.render_mgr.CategoryChanged(cat_ids=[cat_id]) self.scatt_mgr.render_mgr.RenderRequest() if update_cat_rast: self.scatt_mgr.iclass_conn.UpdateCategoryRaster( cat_id, update_cat_rast) self.setCategoryAttrs.emit(cat_id=cat_id, attrs_dict=attrs_dict) def DeleteCategory(self, cat_id): if self.scatt_mgr.data_set: self.scatt_mgr.thread.Run(callable=self.core.DeleteCategory, cat_id=cat_id) del self.cats[cat_id] self.cats_ids.remove(cat_id) self.deletedCategory.emit(cat_id=cat_id) # TODO emit event? def SetSelectedCat(self, cat_id): self.sel_cat_id = cat_id if self.scatt_mgr.pol_sel_mode[0]: self.scatt_mgr.render_mgr.RenderRequest() def GetSelectedCat(self): return self.sel_cat_id def GetCategoryAttrs(self, cat_id): #TODO is mutable return self.cats[cat_id] def GetCategoriesAttrs(self): #TODO is mutable return self.cats def GetCategories(self): return self.cats_ids[:] def SetCategoryPosition(self): if newindex > oldindex: newindex -= 1 self.cats_ids.insert(newindex, self.cats_ids.pop(oldindex)) def ExportCatRast(self, cat_id): cat_attrs = self.GetCategoryAttrs(cat_id) dlg = ExportCategoryRaster( parent=self.scatt_mgr.guiparent, rasterName=self.exportRaster, title=_("Export scatter plot raster of class <%s>") % cat_attrs['name']) if dlg.ShowModal() == wx.ID_OK: self.exportCatRast = dlg.GetRasterName() dlg.Destroy() self.scatt_mgr.thread.Run(callable=self.core.ExportCatRast, userdata={'name': cat_attrs['name']}, cat_id=cat_id, rast_name=self.exportCatRast, ondone=self.OnExportCatRastDone) def OnExportCatRastDone(self, event): ret, err = event.ret if ret == 0: cat_attrs = self.GetCategoryAttrs(event.kwds['cat_id']) GMessage( _("Scatter plot raster of class <%s> exported to raster map <%s>.") % (event.userdata['name'], event.kwds['rast_name'])) else: GMessage( _("Export of scatter plot raster of class <%s> to map <%s> failed.\n%s") % (event.userdata['name'], event.kwds['rast_name'], err))
class SQLBuilderUpdate(SQLBuilder): """Class for building UPDATE SQL statement""" def __init__(self, parent, vectmap, id=wx.ID_ANY, layer=1, column=None): self.column = column # set dialog title title = _("GRASS SQL Builder (%(type)s) - <%(map)s>") % \ { 'type' : "UPDATE", 'map' : vectmap } modeChoices = [ _("Column to set (SET clause)"), _("Constraint for query (WHERE clause)"), _("Calculate column value to set") ] SQLBuilder.__init__(self, parent, title, vectmap, id=wx.ID_ANY, modeChoices=modeChoices, layer=layer) # signals self.sqlApplied = Signal("SQLBuilder.sqlApplied") if parent: # TODO: replace by giface self.sqlApplied.connect(parent.Update) def _doLayout(self, modeChoices): """Do dialog layout""" SQLBuilder._doLayout(self, modeChoices) self.initText = "UPDATE %s SET" % self.tablename if self.column: self.initText += " %s = " % self.column self.text_sql.SetValue(self.initText) self.btn_arithmetic = { 'eq': [ '=', ], 'brac': [ '()', ], 'plus': [ '+', ], 'minus': [ '-', ], 'divide': [ '/', ], 'multiply': [ '*', ] } self.btn_arithmeticpanel = wx.Panel(parent=self.panel, id=wx.ID_ANY) for key, value in self.btn_arithmetic.iteritems(): btn = wx.Button(parent=self.btn_arithmeticpanel, id=wx.ID_ANY, label=value[0]) self.btn_arithmetic[key].append(btn.GetId()) btn_arithmeticsizer = wx.GridBagSizer(hgap=5, vgap=5) btn_arithmeticsizer.Add(item=self.FindWindowById( self.btn_arithmetic['eq'][1]), pos=(0, 0)) btn_arithmeticsizer.Add(item=self.FindWindowById( self.btn_arithmetic['brac'][1]), pos=(1, 0)) btn_arithmeticsizer.Add(item=self.FindWindowById( self.btn_arithmetic['plus'][1]), pos=(0, 1)) btn_arithmeticsizer.Add(item=self.FindWindowById( self.btn_arithmetic['minus'][1]), pos=(1, 1)) btn_arithmeticsizer.Add(item=self.FindWindowById( self.btn_arithmetic['divide'][1]), pos=(0, 2)) btn_arithmeticsizer.Add(item=self.FindWindowById( self.btn_arithmetic['multiply'][1]), pos=(1, 2)) self.btn_arithmeticpanel.SetSizer(btn_arithmeticsizer) self.pagesizer.Insert(item=self.btn_arithmeticpanel, before=3, proportion=0, flag=wx.ALIGN_CENTER_HORIZONTAL) self.funcpanel = wx.Panel(parent=self.panel, id=wx.ID_ANY) self._initSqlFunctions() funcsbox = wx.StaticBox(parent=self.funcpanel, id=wx.ID_ANY, label=" %s " % _("Functions")) funcsizer = wx.StaticBoxSizer(funcsbox, wx.VERTICAL) self.list_func = wx.ListBox(parent=self.funcpanel, id=wx.ID_ANY, choices=self.sqlFuncs['sqlite'].keys(), style=wx.LB_SORT) funcsizer.Add(item=self.list_func, proportion=1, flag=wx.EXPAND) self.funcpanel.SetSizer(funcsizer) self.hsizer.Insert(item=self.funcpanel, before=2, proportion=1, flag=wx.EXPAND) self.list_func.Bind(wx.EVT_LISTBOX, self.OnAddFunc) for key, value in self.btn_arithmetic.iteritems(): self.FindWindowById(value[1]).Bind(wx.EVT_BUTTON, self.OnAddMark) self.mode.SetSelection(0) self.OnMode(None) self.text_sql.SetInsertionPoint(self.text_sql.GetLastPosition()) def OnApply(self, event): """Apply button pressed""" ret, msg = RunCommand('db.execute', getErrorMsg=True, parent=self, stdin=self.text_sql.GetValue(), input='-', driver=self.driver, database=self.database) if ret != 0 and msg: self.statusbar.SetStatusText(_("SQL statement was not applied"), 0) else: self.statusbar.SetStatusText(_("SQL statement applied"), 0) self.sqlApplied.emit() def OnClear(self, event): """Clear button pressed""" self.text_sql.SetValue(self.initText) def OnMode(self, event): """Adjusts builder for chosen mode""" if self.mode.GetSelection() == 0: self.valuespanel.Hide() self.btn_logicpanel.Hide() self.btn_arithmeticpanel.Hide() self.funcpanel.Hide() elif self.mode.GetSelection() == 1: self.valuespanel.Show() self.btn_logicpanel.Show() self.btn_arithmeticpanel.Hide() self.funcpanel.Hide() elif self.mode.GetSelection() == 2: self.valuespanel.Hide() self.btn_logicpanel.Hide() self.btn_arithmeticpanel.Show() self.funcpanel.Show() self.pagesizer.Layout() def OnAddFunc(self, event): """Add function to the query""" if self.driver == 'dbf': GMessage( parent=self, message=_( "Dbf driver does not support usage of SQL functions.")) return idx = self.list_func.GetSelections() for i in idx: func = self.sqlFuncs['sqlite'][self.list_func.GetString(i)][0] self._add(element='func', value=func) def _add(self, element, value): """Add element to the query :param element: element to add (column, value) """ sqlstr = self.text_sql.GetValue() curspos = self.text_sql.GetInsertionPoint() newsqlstr = '' if element in ['value', 'mark', 'func'] or \ (element == 'column' and self.mode.GetSelection() == 2): addstr = ' ' + value + ' ' newsqlstr = sqlstr[:curspos] + addstr + sqlstr[curspos:] curspos += len(addstr) elif element == 'column': if self.mode.GetSelection() == 0: # -> column idx1 = sqlstr.lower().find('set') + len('set') idx2 = sqlstr.lower().find('where') if idx2 >= 0: colstr = sqlstr[idx1:idx2].strip() else: colstr = sqlstr[idx1:].strip() cols = [col.split('=')[0].strip() for col in colstr.split(',')] if unicode(value) in cols: self.text_sql.SetInsertionPoint(curspos) wx.CallAfter(self.text_sql.SetFocus) return if colstr: colstr += ',' colstr = ' ' + colstr colstr += ' ' + value + '= ' newsqlstr = sqlstr[:idx1] + colstr if idx2 >= 0: newsqlstr += sqlstr[idx2:] curspos = idx1 + len(colstr) elif self.mode.GetSelection() == 1: # -> where newsqlstr = '' if sqlstr.lower().find('where') < 0: newsqlstr += ' WHERE' newsqlstr += ' ' + value curspos = self.text_sql.GetLastPosition() + len(newsqlstr) newsqlstr = sqlstr + newsqlstr if newsqlstr: self.text_sql.SetValue(newsqlstr) wx.CallAfter(self.text_sql.SetFocus) self.text_sql.SetInsertionPoint(curspos) def _initSqlFunctions(self): self.sqlFuncs = {} # TODO add functions for other drivers self.sqlFuncs['sqlite'] = { 'ABS': ['ABS()'], 'LENGTH': ['LENGTH()'], 'LOWER': ['LOWER()'], 'LTRIM': ['LTRIM(,)'], 'MAX': ['MAX()'], 'MIN': ['MIN()'], 'RTRIM': ['RTRIM(,)'], 'SUBSTR': ['SUBSTR (,[,])'], 'TRIM': ['TRIM (,)'] }
class SearchModuleWidget(wx.Panel): """Search module widget (used e.g. in SearchModuleWindow) Signals: moduleSelected - attribute 'name' is module name showSearchResult - attribute 'result' is a node (representing module) showNotification - attribute 'message' """ def __init__(self, parent, model, showChoice=True, showTip=False, **kwargs): self._showTip = showTip self._showChoice = showChoice self._model = model self._results = [] # list of found nodes self._resultIndex = -1 self._searchKeys = ['description', 'keywords', 'command'] self._oldValue = '' self.moduleSelected = Signal('SearchModuleWidget.moduleSelected') self.showSearchResult = Signal('SearchModuleWidget.showSearchResult') self.showNotification = Signal('SearchModuleWidget.showNotification') wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY, **kwargs) # self._box = wx.StaticBox(parent = self, id = wx.ID_ANY, # label = " %s " % _("Find module - (press Enter for next match)")) if sys.platform == 'win32': self._search = wx.TextCtrl(parent = self, id = wx.ID_ANY, size = (-1, 25), style = wx.TE_PROCESS_ENTER) else: self._search = wx.SearchCtrl(parent = self, id = wx.ID_ANY, size = (-1, 25), style = wx.TE_PROCESS_ENTER) self._search.SetDescriptiveText(_('Fulltext search')) self._search.SetToolTipString(_("Type to search in all modules. Press Enter for next match.")) self._search.Bind(wx.EVT_TEXT, self.OnSearchModule) self._search.Bind(wx.EVT_KEY_UP, self.OnKeyUp) if self._showTip: self._searchTip = StaticWrapText(parent = self, id = wx.ID_ANY, size = (-1, 35)) if self._showChoice: self._searchChoice = wx.Choice(parent=self, id=wx.ID_ANY) self._searchChoice.SetItems(self._searchModule(keys=['command'], value='')) self._searchChoice.Bind(wx.EVT_CHOICE, self.OnSelectModule) self._layout() def _layout(self): """Do layout""" sizer = wx.BoxSizer(wx.HORIZONTAL) boxSizer = wx.BoxSizer(wx.VERTICAL) boxSizer.Add(item=self._search, flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND | wx.BOTTOM, border=5) if self._showChoice: hSizer = wx.BoxSizer(wx.HORIZONTAL) hSizer.Add(item=self._searchChoice, flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND | wx.BOTTOM, border=5) hSizer.AddStretchSpacer() boxSizer.Add(item=hSizer, flag=wx.EXPAND) if self._showTip: boxSizer.Add(item=self._searchTip, flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND) sizer.Add(item=boxSizer, proportion=1) self.SetSizer(sizer) sizer.Fit(self) def OnKeyUp(self, event): """Key or key combination pressed""" if event.GetKeyCode() in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER) and not event.ControlDown(): if self._results: self._resultIndex += 1 if self._resultIndex == len(self._results): self._resultIndex = 0 self.showSearchResult.emit(result=self._results[self._resultIndex]) event.Skip() def OnSearchModule(self, event): """Search module by keywords or description""" value = self._search.GetValue() if value == self._oldValue: event.Skip() return self._oldValue = value if len(value) <= 2: if len(value) == 0: # reset commands = self._searchModule(keys=['command'], value='') else: self.showNotification.emit(message=_("Searching, please type more characters.")) return else: commands = self._searchModule(keys=self._searchKeys, value=value) if self._showChoice: self._searchChoice.SetItems(commands) if commands: self._searchChoice.SetSelection(0) label = _("%d modules match") % len(commands) if self._showTip: self._searchTip.SetLabel(label) self.showNotification.emit(message=label) event.Skip() def _searchModule(self, keys, value): """Search modules by keys :param keys: list of keys :param value: patter to match """ nodes = set() for key in keys: nodes.update(self._model.SearchNodes(key=key, value=value)) nodes = list(nodes) nodes.sort(key=lambda node: self._model.GetIndexOfNode(node)) self._results = nodes self._resultIndex = -1 commands = [node.data['command'] for node in nodes if node.data['command']] commands.sort() # return sorted list of commands (TODO: sort in better way) return commands def OnSelectModule(self, event): """Module selected from choice, update command prompt""" cmd = self._searchChoice.GetStringSelection() self.moduleSelected.emit(name=cmd) if self._showTip: for module in self._results: if cmd == module.data['command']: self._searchTip.SetLabel(module.data['description']) break def Reset(self): """Reset widget""" self._search.SetValue('') if self._showTip: self._searchTip.SetLabel('')
class PlotsRenderingManager: """Manages rendering of scatter plot. .. todo:: still space for optimalization """ def __init__(self, scatt_mgr, cats_mgr, core): self.scatt_mgr = scatt_mgr self.cats_mgr = cats_mgr self.core = core self.scatts_dt, self.scatt_conds_dt = self.core.GetScattsData() self.runningProcesses = 0 self.data_to_render = {} self.render_queue = [] self.cat_ids = [] self.cat_cond_ids = [] self.renderingStarted = Signal("ScattsManager.renderingStarted") self.renderingFinished = Signal("ScattsManager.renderingFinished") def AddRenderRequest(self, scatts): for scatt_id, cat_ids in scatts: if not self.data_to_render.has_key[scatt_id]: self.data_to_render = cat_ids else: for c in cat_ids: if c not in self.data_to_render[scatt_id]: self.data_to_render[scatt_id].append(c) def NewRunningProcess(self): self.runningProcesses += 1 def RunningProcessDone(self): self.runningProcesses -= 1 if self.runningProcesses <= 1: self.RenderScattPlts() def RenderRequest(self): if self.runningProcesses <= 1: self.RenderScattPlts() def CategoryChanged(self, cat_ids): for c in cat_ids: if c not in self.cat_ids: self.cat_ids.append(c) def CategoryCondsChanged(self, cat_ids): for c in cat_ids: if c not in self.cat_cond_ids: self.cat_cond_ids.append(c) def RenderScattPlts(self, scatt_ids=None): if len(self.render_queue) > 1: return self.renderingStarted.emit() self.render_queue.append(self.scatt_mgr.thread.GetId()) cats_attrs = deepcopy(self.cats_mgr.GetCategoriesAttrs()) cats = self.cats_mgr.GetCategories()[:] self.scatt_mgr.thread.Run( callable=self._renderscattplts, scatt_ids=scatt_ids, cats=cats, cats_attrs=cats_attrs, ondone=self.RenderingDone) def _renderscattplts(self, scatt_ids, cats, cats_attrs): cats.reverse() cats.insert(0, 0) for i_scatt_id, scatt in self.scatt_mgr.plots.items(): if scatt_ids is not None and \ i_scatt_id not in scatt_ids: continue if not scatt['scatt']: continue scatt_dt = self.scatts_dt.GetScatt(i_scatt_id) if self._showConfEllipses(): ellipses_dt = self.scatts_dt.GetEllipses( i_scatt_id, cats_attrs) else: ellipses_dt = {} for c in scatt_dt.iterkeys(): try: self.cat_ids.remove(c) scatt_dt[c]['render'] = True except: scatt_dt[c]['render'] = False if self.scatt_mgr.pol_sel_mode[0]: self._getSelectedAreas(cats, i_scatt_id, scatt_dt, cats_attrs) scatt['scatt'].Plot(cats_order=cats, scatts=scatt_dt, ellipses=ellipses_dt, styles=cats_attrs) def RenderingDone(self, event): self.render_queue.remove(event.pid) if not self.render_queue: self.renderingFinished.emit() def _getSelectedAreas(self, cats_order, scatt_id, scatt_dt, cats_attrs): cat_id = self.cats_mgr.GetSelectedCat() if not cat_id: return sel_a_cat_id = -1 s = self.scatt_conds_dt.GetScatt(scatt_id, [cat_id]) if not s: return cats_order.append(sel_a_cat_id) col = UserSettings.Get(group='scatt', key='selection', subkey='sel_area') col = ":".join(map(str, col)) opac = UserSettings.Get(group='scatt', key='selection', subkey='sel_area_opacty') / 100.0 cats_attrs[sel_a_cat_id] = {'color': col, 'opacity': opac, 'show': True} scatt_dt[sel_a_cat_id] = s[cat_id] scatt_dt[sel_a_cat_id]['render'] = False if cat_id in self.cat_cond_ids: scatt_dt[sel_a_cat_id]['render'] = True self.cat_cond_ids.remove(cat_id) def _showConfEllipses(self): return UserSettings.Get(group='scatt', key="ellipses", subkey="show_ellips")
class RLiSetupMapPanel(wx.Panel): """Panel with mapwindow used in r.li.setup""" def __init__(self, parent, samplingType, icon=None, map_=None): wx.Panel.__init__(self, parent=parent) self.mapWindowProperties = MapWindowProperties() self.mapWindowProperties.setValuesFromUserSettings() giface = StandaloneGrassInterface() self.samplingtype = samplingType self.parent = parent if map_: self.map_ = map_ else: self.map_ = Map() self.map_.region = self.map_.GetRegion() self._mgr = wx.aui.AuiManager(self) self.mapWindow = BufferedMapWindow(parent=self, giface=giface, Map=self.map_, properties=self.mapWindowProperties) self._mgr.AddPane(self.mapWindow, wx.aui.AuiPaneInfo().CentrePane(). Dockable(True).BestSize((-1, -1)).Name('mapwindow'). CloseButton(False).DestroyOnClose(True). Layer(0)) self._toolSwitcher = ToolSwitcher() self._toolSwitcher.toggleToolChanged.connect(self._onToolChanged) self.toolbar = RLiSetupToolbar(self, self._toolSwitcher) self.catId = 1 self._mgr.AddPane(self.toolbar, wx.aui.AuiPaneInfo(). Name("maptoolbar").Caption(_("Map Toolbar")). ToolbarPane().Left().Name('mapToolbar'). CloseButton(False).Layer(1).Gripper(False). BestSize((self.toolbar.GetBestSize()))) self._mgr.Update() if self.samplingtype == SamplingType.REGIONS: self.afterRegionDrawn = Signal('RLiSetupMapPanel.afterRegionDrawn') self._registeredGraphics = self.mapWindow.RegisterGraphicsToDraw( graphicsType='line') elif self.samplingtype in [SamplingType.MUNITSR, SamplingType.MMVWINR]: self.sampleFrameChanged = Signal( 'RLiSetupMapPanel.sampleFrameChanged') self._registeredGraphics = self.mapWindow.RegisterGraphicsToDraw( graphicsType='rectangle') elif self.samplingtype in [SamplingType.MUNITSC, SamplingType.MMVWINC]: self.afterCircleDrawn = Signal('RLiSetupMapPanel.afterCircleDrawn') self._registeredGraphics = self.mapWindow.RegisterGraphicsToDraw( graphicsType='line') else: self.sampleFrameChanged = Signal( 'RLiSetupMapPanel.sampleFrameChanged') self._registeredGraphics = self.mapWindow.RegisterGraphicsToDraw( graphicsType='rectangle') self._registeredGraphics.AddPen('rlisetup', wx.Pen(wx.GREEN, width=2, style=wx.SOLID)) self._registeredGraphics.AddItem(coords=[[0, 0], [0, 0]], penName='rlisetup', hide=True) if self.samplingtype != SamplingType.VECT: self.toolbar.SelectDefault() def GetMap(self): return self.map_ def OnPan(self, event): """Panning, set mouse to drag.""" self.mapWindow.SetModePan() def OnZoomIn(self, event): """Zoom in the map.""" self.mapWindow.SetModeZoomIn() def OnZoomOut(self, event): """Zoom out the map.""" self.mapWindow.SetModeZoomOut() def OnZoomToMap(self, event): layers = self.map_.GetListOfLayers() self.mapWindow.ZoomToMap(layers=layers, ignoreNulls=False, render=True) def OnDrawRadius(self, event): """Start draw mode""" self.mapWindow.mouse['use'] = "None" self.mapWindow.mouse['box'] = "line" self.mapWindow.pen = wx.Pen(colour=wx.RED, width=1, style=wx.SHORT_DASH) self.mapWindow.SetNamedCursor('cross') self.mapWindow.mouseLeftUp.connect(self._radiusDrawn) def OnDigitizeRegion(self, event): """Start draw mode""" self.mapWindow.mouse['use'] = "None" self.mapWindow.mouse['box'] = "line" self.mapWindow.pen = wx.Pen(colour=wx.RED, width=1, style=wx.SHORT_DASH) self.mapWindow.SetNamedCursor('cross') self.mapWindow.mouseLeftUp.connect(self._lineSegmentDrawn) self.mapWindow.mouseDClick.connect(self._mouseDbClick) self._registeredGraphics.GetItem(0).SetCoords([]) def OnDraw(self, event): """Start draw mode""" self.mapWindow.mouse['use'] = "None" self.mapWindow.mouse['box'] = "box" self.mapWindow.pen = wx.Pen(colour=wx.RED, width=2, style=wx.SHORT_DASH) self.mapWindow.SetNamedCursor('cross') self.mapWindow.mouseLeftUp.connect(self._rectangleDrawn) def _lineSegmentDrawn(self, x, y): item = self._registeredGraphics.GetItem(0) coords = item.GetCoords() if len(coords) == 0: coords.extend([self.mapWindow.Pixel2Cell( self.mapWindow.mouse['begin'])]) coords.extend([[x, y]]) item.SetCoords(coords) item.SetPropertyVal('hide', False) self.mapWindow.ClearLines() self._registeredGraphics.Draw() def _mouseDbClick(self, x, y): item = self._registeredGraphics.GetItem(0) coords = item.GetCoords() coords.extend([[x, y]]) item.SetCoords(coords) item.SetPropertyVal('hide', False) self.mapWindow.ClearLines() self._registeredGraphics.Draw() self.createRegion() def createRegion(self): dlg = wx.TextEntryDialog(None, 'Name of sample region', 'Create region', 'region' + str(self.catId)) ret = dlg.ShowModal() while True: if ret == wx.ID_OK: raster = dlg.GetValue() if checkMapExists(raster): GMessage( parent=self, message=_( "The raster file %s already" " exists, please change name") % raster) ret = dlg.ShowModal() else: dlg.Destroy() marea = self.writeArea( self._registeredGraphics.GetItem(0).GetCoords(), raster) self.nextRegion(next=True, area=marea) break else: self.nextRegion(next=False) break def nextRegion(self, next=True, area=None): self.mapWindow.ClearLines() item = self._registeredGraphics.GetItem(0) item.SetCoords([]) item.SetPropertyVal('hide', True) layers = self.map_.GetListOfLayers() self.mapWindow.ZoomToMap(layers=layers, ignoreNulls=False, render=True) if next is True: self.afterRegionDrawn.emit(marea=area) else: gcmd.GMessage(parent=self.parent, message=_( "Raster map not created. Please redraw region.")) def writeArea(self, coords, rasterName): polyfile = tempfile.NamedTemporaryFile(delete=False) polyfile.write("AREA\n") for coor in coords: east, north = coor point = " %s %s\n" % (east, north) polyfile.write(point) catbuf = "=%d a\n" % self.catId polyfile.write(catbuf) self.catId = self.catId + 1 polyfile.close() region_settings = grass.parse_command('g.region', flags='p', delimiter=':') pname = polyfile.name.split('/')[-1] tmpraster = "rast_" + pname tmpvector = "vect_" + pname wx.BeginBusyCursor() wx.GetApp().Yield() RunCommand('r.in.poly', input=polyfile.name, output=tmpraster, rows=region_settings['rows'], overwrite=True) RunCommand('r.to.vect', input=tmpraster, output=tmpvector, type='area', overwrite=True) RunCommand('v.to.rast', input=tmpvector, output=rasterName, value=1, use='val') wx.EndBusyCursor() grass.use_temp_region() grass.run_command('g.region', vector=tmpvector) region = grass.region() marea = MaskedArea(region, rasterName) RunCommand('g.remove', flags='f', type='raster', name=tmpraster) RunCommand('g.remove', flags='f', type='vector', name=tmpvector) os.unlink(polyfile.name) return marea def _onToolChanged(self): """Helper function to disconnect drawing""" try: self.mapWindow.mouseLeftUp.disconnect(self._rectangleDrawn) self.mapWindow.mouseLeftUp.disconnect(self._radiusDrawn) self.mapWindow.mouseMoving.disconnect(self._mouseMoving) self.mapWindow.mouseLeftDown.disconnect(self._mouseLeftDown) self.mapWindow.mouseDClick.disconnect(self._mouseDbClick) except DispatcherKeyError: pass def _radiusDrawn(self, x, y): """When drawing finished, get region values""" mouse = self.mapWindow.mouse item = self._registeredGraphics.GetItem(0) p1 = mouse['begin'] p2 = mouse['end'] dist, (north, east) = self.mapWindow.Distance(p1, p2, False) circle = Circle(p1, dist) self.mapWindow.ClearLines() self.mapWindow.pdcTmp.SetBrush(wx.Brush(wx.CYAN, wx.TRANSPARENT)) pen = wx.Pen(colour=wx.RED, width=2) self.mapWindow.pdcTmp.SetPen(pen) self.mapWindow.pdcTmp.DrawCircle(circle.point[0], circle.point[1], circle.radius) self._registeredGraphics.Draw() self.createCricle(circle) def createCricle(self, c): dlg = wx.TextEntryDialog(None, 'Name of sample circle region', 'Create circle region', 'circle' + str(self.catId)) ret = dlg.ShowModal() while True: if ret == wx.ID_OK: raster = dlg.GetValue() if checkMapExists(raster): GMessage( parent=self, message=_( "The raster file %s already" " exists, please change name") % raster) ret = dlg.ShowModal() else: dlg.Destroy() circle = self.writeCircle(c, raster) self.nextCircle(next=True, circle=circle) break else: self.nextCircle(next=False) break def nextCircle(self, next=True, circle=None): self.mapWindow.ClearLines() item = self._registeredGraphics.GetItem(0) item.SetPropertyVal('hide', True) layers = self.map_.GetListOfLayers() self.mapWindow.ZoomToMap(layers=layers, ignoreNulls=False, render=True) if next is True: self.afterCircleDrawn.emit(region=circle) else: gcmd.GMessage(parent=self.parent, message=_( "Raster map not created. redraw region again.")) def writeCircle(self, circle, rasterName): coords = self.mapWindow.Pixel2Cell(circle.point) RunCommand('r.circle', output=rasterName, max=circle.radius, coordinate=coords, flags="b") grass.use_temp_region() grass.run_command('g.region', zoom=rasterName) region = grass.region() marea = MaskedArea(region, rasterName, circle.radius) return marea def _rectangleDrawn(self): """When drawing finished, get region values""" mouse = self.mapWindow.mouse item = self._registeredGraphics.GetItem(0) p1 = self.mapWindow.Pixel2Cell(mouse['begin']) p2 = self.mapWindow.Pixel2Cell(mouse['end']) item.SetCoords([p1, p2]) region = {'n': max(p1[1], p2[1]), 's': min(p1[1], p2[1]), 'w': min(p1[0], p2[0]), 'e': max(p1[0], p2[0])} item.SetPropertyVal('hide', False) self.mapWindow.ClearLines() self._registeredGraphics.Draw() if self.samplingtype in [SamplingType.MUNITSR, SamplingType.MMVWINR]: dlg = wx.MessageDialog(self, "Is this area ok?", "select sampling unit", wx.YES_NO | wx.ICON_QUESTION) ret = dlg.ShowModal() if ret == wx.ID_YES: grass.use_temp_region() grass.run_command('g.region', n=region['n'], s=region['s'], e=region['e'], w=region['w']) tregion = grass.region() self.sampleFrameChanged.emit(region=tregion) self.mapWindow.ClearLines() item = self._registeredGraphics.GetItem(0) item.SetPropertyVal('hide', True) layers = self.map_.GetListOfLayers() self.mapWindow.ZoomToMap(layers=layers, ignoreNulls=False, render=True) else: self.nextRegion(next=False) dlg.Destroy() elif self.samplingtype != SamplingType.WHOLE: """When drawing finished, get region values""" self.sampleFrameChanged.emit(region=region)
class Animation: """Class represents animation as a sequence of states (views). It enables to record, replay the sequence and finally generate all image files. Recording and replaying is based on timer events. There is no frame interpolation like in the Tcl/Tk based Nviz. """ def __init__(self, mapWindow, timer): """Animation constructor Signals: animationFinished - emitted when animation finished - attribute 'mode' animationUpdateIndex - emitted during animation to update gui - attributes 'index' and 'mode' :param mapWindow: glWindow where rendering takes place :param timer: timer for recording and replaying """ self.animationFinished = Signal("Animation.animationFinished") self.animationUpdateIndex = Signal("Animation.animationUpdateIndex") self.animationList = [] # view states self.timer = timer self.mapWindow = mapWindow self.actions = {"record": self.Record, "play": self.Play} self.formats = ["tif", "ppm"] # currently supported formats self.mode = "record" # current mode (record, play, save) self.paused = False # recording/replaying paused self.currentFrame = 0 # index of current frame self.fps = 24 # user settings # Frames per second self.stopSaving = False # stop during saving images self.animationSaved = False # current animation saved or not def Start(self): """Start recording/playing""" self.timer.Start(self.GetInterval()) def Pause(self): """Pause recording/playing""" self.timer.Stop() def Stop(self): """Stop recording/playing""" self.timer.Stop() self.PostFinishedEvent() def Update(self): """Record/play next view state (on timer event)""" self.actions[self.mode]() def Record(self): """Record new view state""" self.animationList.append({ "view": copy.deepcopy(self.mapWindow.view), "iview": copy.deepcopy(self.mapWindow.iview), }) self.currentFrame += 1 self.PostUpdateIndexEvent(index=self.currentFrame) self.animationSaved = False def Play(self): """Render next frame""" if not self.animationList: self.Stop() return try: self.IterAnimation() except IndexError: # no more frames self.Stop() def IterAnimation(self): params = self.animationList[self.currentFrame] self.UpdateView(params) self.currentFrame += 1 self.PostUpdateIndexEvent(index=self.currentFrame) def UpdateView(self, params): """Update view data in map window and render""" toolWin = self.mapWindow.GetToolWin() toolWin.UpdateState(view=params["view"], iview=params["iview"]) self.mapWindow.UpdateView() self.mapWindow.render["quick"] = True self.mapWindow.Refresh(False) def IsRunning(self): """Test if timer is running""" return self.timer.IsRunning() def SetMode(self, mode): """Start animation mode :param mode: animation mode (record, play, save) """ self.mode = mode def GetMode(self): """Get animation mode (record, play, save)""" return self.mode def IsPaused(self): """Test if animation is paused""" return self.paused def SetPause(self, pause): self.paused = pause def Exists(self): """Returns if an animation has been recorded""" return bool(self.animationList) def GetFrameCount(self): """Return number of recorded frames""" return len(self.animationList) def Clear(self): """Clear all records""" self.animationList = [] self.currentFrame = 0 def GoToFrame(self, index): """Render frame of given index""" if index >= len(self.animationList): return self.currentFrame = index params = self.animationList[self.currentFrame] self.UpdateView(params) def PostFinishedEvent(self): """Animation ends""" self.animationFinished.emit(mode=self.mode) def PostUpdateIndexEvent(self, index): """Frame index changed, update tool window""" self.animationUpdateIndex(index=index, mode=self.mode) def StopSaving(self): """Abort image files generation""" self.stopSaving = True def IsSaved(self): """Test if animation has been saved (to images)""" return self.animationSaved def SaveAnimationFile(self, path, prefix, format): """Generate image files :param path: path to direcory :param prefix: file prefix :param format: index of image file format """ size = self.mapWindow.GetClientSize() toolWin = self.mapWindow.GetToolWin() formatter = ":04.0f" n = len(self.animationList) if n < 10: formatter = ":01.0f" elif n < 100: formatter = ":02.0f" elif n < 1000: formatter = ":03.0f" self.currentFrame = 0 self.mode = "save" for params in self.animationList: if not self.stopSaving: self.UpdateView(params) number = ("{frame" + formatter + "}").format(frame=self.currentFrame) filename = "{prefix}_{number}.{ext}".format( prefix=prefix, number=number, ext=self.formats[format]) filepath = os.path.join(path, filename) self.mapWindow.SaveToFile( FileName=filepath, FileType=self.formats[format], width=size[0], height=size[1], ) self.currentFrame += 1 wx.GetApp().Yield() toolWin.UpdateFrameIndex(index=self.currentFrame, goToFrame=False) else: self.stopSaving = False break self.animationSaved = True self.PostFinishedEvent() def SetFPS(self, fps): """Set Frames Per Second value :param fps: frames per second """ self.fps = fps def GetInterval(self): """Return timer interval in ms""" return 1000.0 / self.fps
class ToolSwitcher: """Class handling switching tools in toolbar and custom toggle buttons.""" def __init__(self): self._groups = defaultdict(lambda: defaultdict(list)) self._toolsGroups = defaultdict(list) # emitted when tool is changed self.toggleToolChanged = Signal('ToolSwitcher.toggleToolChanged') def AddToolToGroup(self, group, toolbar, tool): """Adds tool from toolbar to group of exclusive tools. :param group: name of group (e.g. 'mouseUse') :param toolbar: instance of toolbar :param tool: id of a tool from the toolbar """ self._groups[group][toolbar].append(tool) self._toolsGroups[tool].append(group) def AddCustomToolToGroup(self, group, btnId, toggleHandler): """Adds custom tool from to group of exclusive tools (some toggle button). :param group: name of group (e.g. 'mouseUse') :param btnId: id of a tool (typically button) :param toggleHandler: handler to be called to switch the button """ self._groups[group]['custom'].append((btnId, toggleHandler)) self._toolsGroups[btnId].append(group) def RemoveCustomToolFromGroup(self, tool): """Removes custom tool from group. :param tool: id of the button """ if not tool in self._toolsGroups: return for group in self._toolsGroups[tool]: self._groups[group]['custom'] = \ [(bid, hdlr) for (bid, hdlr) in self._groups[group]['custom'] if bid != tool] def RemoveToolbarFromGroup(self, group, toolbar): """Removes toolbar from group. Before toolbar is destroyed, it must be removed from group, too. Otherwise we can expect some DeadObject errors. :param group: name of group (e.g. 'mouseUse') :param toolbar: instance of toolbar """ for tb in self._groups[group]: if tb == toolbar: del self._groups[group][tb] break def IsToolInGroup(self, tool, group): """Checks whether a tool is in a specified group. :param tool: tool id :param group: name of group (e.g. 'mouseUse') """ for group in self._toolsGroups[tool]: for tb in self._groups[group]: if tb == 'custom': for bid, handler in self._groups[group][tb]: if tool == bid: return True elif tb.FindById(tool): return True return False def ToolChanged(self, tool): """When any tool/button is pressed, other tools from group must be unchecked. :param tool: id of a tool/button """ for group in self._toolsGroups[tool]: for tb in self._groups[group]: if tb == 'custom': for btnId, handler in self._groups[group][tb]: if btnId != tool: handler(False) else: for tl in self._groups[group][tb]: if tb.FindById(tl): # check if still exists if tl != tool: tb.ToggleTool(tl, False) else: tb.ToggleTool(tool, True) self.toggleToolChanged.emit(id=tool)
class ScanningPanel(wx.Panel): def __init__(self, parent, giface, settings): wx.Panel.__init__(self, parent) self.giface = giface self.settings = settings self.scan = self.settings['scan'] self.settingsChanged = Signal('ScanningPanel.settingsChanged') if 'export' in self.settings and self.settings['export']['file']: path = self.settings['export']['file'] initDir = os.path.dirname(path) else: path = initDir = "" mainSizer = wx.BoxSizer(wx.VERTICAL) hSizer = wx.BoxSizer(wx.HORIZONTAL) # create widgets self.scan_name_ctrl = wx.TextCtrl(self, value='scan') # widgets for model self.elevInput = Select(self, size=(-1, -1), type='raster') self.regionInput = Select(self, size=(-1, -1), type='region') self.zexag = wx.TextCtrl(self) self.numscans = wx.SpinCtrl(self, min=1, max=5, initial=1) self.rotate = wx.SpinCtrl(self, min=0, max=360, initial=180) self.smooth = wx.TextCtrl(self) self.resolution = wx.TextCtrl(self) self.trim = {} for each in 'tbnsew': self.trim[each] = wx.TextCtrl(self, size=(40, -1)) self.trim_tolerance = wx.TextCtrl(self) self.interpolate = wx.CheckBox(self, label="Use interpolation instead of binning") self.equalize = wx.CheckBox(self, label="Use equalized color table for scan") self.ifExport = wx.CheckBox(self, label="") self.exportPLY = filebrowse.FileBrowseButton(self, labelText="Export PLY:", fileMode=wx.SAVE, startDirectory=initDir, initialValue=path, changeCallback=self.OnModelProperties) self.elevInput.SetValue(self.scan['elevation']) self.regionInput.SetValue(self.scan['region']) self.zexag.SetValue(str(self.scan['zexag'])) self.rotate.SetValue(self.scan['rotation_angle']) self.numscans.SetValue(self.scan['numscans']) self.interpolate.SetValue(self.scan['interpolate']) for i, each in enumerate('nsewtb'): self.trim[each].SetValue(self.scan['trim_nsewtb'].split(',')[i]) self.equalize.SetValue(self.scan['equalize']) self.smooth.SetValue(str(self.scan['smooth'])) self.resolution.SetValue(str(self.scan['resolution'])) self.trim_tolerance.SetValue(str(self.scan['trim_tolerance'])) self.ifExport.SetValue(True if self.settings['export']['active'] else False) # layout mainSizer.Add(hSizer, flag=wx.EXPAND) hSizer = wx.BoxSizer(wx.HORIZONTAL) hSizer.Add(wx.StaticText(self, label="Name of scanned raster:"), flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=3) hSizer.Add(self.scan_name_ctrl, proportion=1, flag=wx.EXPAND | wx.ALL, border=5) mainSizer.Add(hSizer, flag=wx.EXPAND) # model parameters hSizer = wx.BoxSizer(wx.HORIZONTAL) hSizer.Add(wx.StaticText(self, label="Reference DEM:"), flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=3) hSizer.Add(self.elevInput, proportion=1, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=3) mainSizer.Add(hSizer, flag=wx.EXPAND) # region hSizer = wx.BoxSizer(wx.HORIZONTAL) hSizer.Add(wx.StaticText(self, label="Reference region:"), flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=3) hSizer.Add(self.regionInput, proportion=1, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=3) mainSizer.Add(hSizer, flag=wx.EXPAND) hSizer = wx.BoxSizer(wx.HORIZONTAL) hSizer.Add(wx.StaticText(self, label="Z-exaggeration:"), proportion=1, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=3) hSizer.Add(self.zexag, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=3) mainSizer.Add(hSizer, flag=wx.EXPAND) # number of scans hSizer = wx.BoxSizer(wx.HORIZONTAL) hSizer.Add(wx.StaticText(self, label="Number of scans:"), proportion=1, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=3) hSizer.Add(self.numscans, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=3) mainSizer.Add(hSizer, flag=wx.EXPAND) hSizer = wx.BoxSizer(wx.HORIZONTAL) hSizer.Add(wx.StaticText(self, label="Rotation angle:"), proportion=1, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=3) hSizer.Add(self.rotate, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=3) mainSizer.Add(hSizer, flag=wx.EXPAND) # smooth hSizer = wx.BoxSizer(wx.HORIZONTAL) hSizer.Add(wx.StaticText(self, label="Smooth value:"), proportion=1, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=3) hSizer.Add(self.smooth, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=3) mainSizer.Add(hSizer, flag=wx.EXPAND) # resolution hSizer = wx.BoxSizer(wx.HORIZONTAL) hSizer.Add(wx.StaticText(self, label="Resolution [mm]:"), proportion=1, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=3) hSizer.Add(self.resolution, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=3) mainSizer.Add(hSizer, flag=wx.EXPAND) hSizer = wx.BoxSizer(wx.HORIZONTAL) hSizer.Add(wx.StaticText(self, label="Limit scan vertically T, B [cm]:"), proportion=1, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=3) for each in 'tb': hSizer.Add(self.trim[each], flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=3) mainSizer.Add(hSizer, flag=wx.EXPAND) hSizer = wx.BoxSizer(wx.HORIZONTAL) hSizer.Add(wx.StaticText(self, label="Trim scan N, S, E, W [cm]:"), proportion=1, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=3) for each in 'nsew': hSizer.Add(self.trim[each], flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=3) mainSizer.Add(hSizer, flag=wx.EXPAND) hSizer = wx.BoxSizer(wx.HORIZONTAL) hSizer.Add(wx.StaticText(self, label="Trim tolerance [0-1]:"), proportion=1, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=3) hSizer.Add(self.trim_tolerance, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=3) mainSizer.Add(hSizer, flag=wx.EXPAND) hSizer = wx.BoxSizer(wx.HORIZONTAL) hSizer.Add(self.interpolate, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=3) mainSizer.Add(hSizer, flag=wx.EXPAND) hSizer = wx.BoxSizer(wx.HORIZONTAL) hSizer.Add(self.equalize, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=3) mainSizer.Add(hSizer, flag=wx.EXPAND) hSizer = wx.BoxSizer(wx.HORIZONTAL) hSizer.Add(self.ifExport, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.LEFT, border=3) hSizer.Add(self.exportPLY, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, proportion=1, border=0) mainSizer.Add(hSizer, flag=wx.EXPAND) self.SetSizer(mainSizer) mainSizer.Fit(self) self.BindModelProperties() def BindModelProperties(self): self.scan_name_ctrl.Bind(wx.EVT_TEXT, self.OnModelProperties) # model parameters self.elevInput.Bind(wx.EVT_TEXT, self.OnModelProperties) self.regionInput.Bind(wx.EVT_TEXT, self.OnModelProperties) self.zexag.Bind(wx.EVT_TEXT, self.OnModelProperties) self.rotate.Bind(wx.EVT_SPINCTRL, self.OnModelProperties) self.rotate.Bind(wx.EVT_TEXT, self.OnModelProperties) self.numscans.Bind(wx.EVT_SPINCTRL, self.OnModelProperties) self.numscans.Bind(wx.EVT_TEXT, self.OnModelProperties) self.interpolate.Bind(wx.EVT_CHECKBOX, self.OnModelProperties) self.equalize.Bind(wx.EVT_CHECKBOX, self.OnModelProperties) self.smooth.Bind(wx.EVT_TEXT, self.OnModelProperties) self.resolution.Bind(wx.EVT_TEXT, self.OnModelProperties) self.trim_tolerance.Bind(wx.EVT_TEXT, self.OnModelProperties) for each in 'nsewtb': self.trim[each].Bind(wx.EVT_TEXT, self.OnModelProperties) self.ifExport.Bind(wx.EVT_CHECKBOX, self.OnModelProperties) def OnModelProperties(self, event): self.scan['scan_name'] = self.scan_name_ctrl.GetValue() self.scan['elevation'] = self.elevInput.GetValue() self.scan['region'] = self.regionInput.GetValue() self.scan['rotation_angle'] = self.rotate.GetValue() self.scan['numscans'] = self.numscans.GetValue() self.scan['interpolate'] = self.interpolate.IsChecked() self.scan['equalize'] = self.equalize.IsChecked() self.scan['smooth'] = self.smooth.GetValue() self.scan['resolution'] = self.resolution.GetValue() self.scan['trim_tolerance'] = self.trim_tolerance.GetValue() if 'export' not in self.settings: self.settings['export'] = {} self.settings['export']['active'] = self.ifExport.IsChecked() self.settings['export']['file'] = self.exportPLY.GetValue() try: self.scan['zexag'] = float(self.zexag.GetValue()) nsewtb_list = [] for each in 'nsewtb': nsewtb_list.append(self.trim[each].GetValue()) self.scan['trim_nsewtb'] = ','.join(nsewtb_list) except ValueError: pass self.settingsChanged.emit()
class RLiSetupMapPanel(wx.Panel): """Panel with mapwindow used in r.li.setup""" def __init__(self, parent, samplingType, icon=None, map_=None): wx.Panel.__init__(self, parent=parent) self.mapWindowProperties = MapWindowProperties() self.mapWindowProperties.setValuesFromUserSettings() giface = StandaloneGrassInterface() self.samplingtype = samplingType self.parent = parent if map_: self.map_ = map_ else: self.map_ = Map() self.map_.region = self.map_.GetRegion() self._mgr = wx.aui.AuiManager(self) self.mapWindow = BufferedMapWindow(parent=self, giface=giface, Map=self.map_, properties=self.mapWindowProperties) self._mgr.AddPane(self.mapWindow, wx.aui.AuiPaneInfo().CentrePane(). Dockable(True).BestSize((-1, -1)).Name('mapwindow'). CloseButton(False).DestroyOnClose(True). Layer(0)) self._toolSwitcher = ToolSwitcher() self._toolSwitcher.toggleToolChanged.connect(self._onToolChanged) self.toolbar = RLiSetupToolbar(self, self._toolSwitcher) self.catId = 1 self._mgr.AddPane(self.toolbar, wx.aui.AuiPaneInfo(). Name("maptoolbar").Caption(_("Map Toolbar")). ToolbarPane().Left().Name('mapToolbar'). CloseButton(False).Layer(1).Gripper(False). BestSize((self.toolbar.GetBestSize()))) self._mgr.Update() if self.samplingtype == SamplingType.REGIONS: self.afterRegionDrawn = Signal('RLiSetupMapPanel.afterRegionDrawn') self._registeredGraphics = self.mapWindow.RegisterGraphicsToDraw(graphicsType='line') elif self.samplingtype in [SamplingType.MUNITSR, SamplingType.MMVWINR]: self.sampleFrameChanged = Signal('RLiSetupMapPanel.sampleFrameChanged') self._registeredGraphics = self.mapWindow.RegisterGraphicsToDraw(graphicsType='rectangle') elif self.samplingtype in [SamplingType.MUNITSC, SamplingType.MMVWINC]: self.afterCircleDrawn = Signal('RLiSetupMapPanel.afterCircleDrawn') self._registeredGraphics = self.mapWindow.RegisterGraphicsToDraw(graphicsType='line') else: self.sampleFrameChanged = Signal('RLiSetupMapPanel.sampleFrameChanged') self._registeredGraphics = self.mapWindow.RegisterGraphicsToDraw(graphicsType='rectangle') self._registeredGraphics.AddPen('rlisetup', wx.Pen(wx.GREEN, width=2, style=wx.SOLID)) self._registeredGraphics.AddItem(coords=[[0, 0], [0, 0]], penName='rlisetup', hide=True) if self.samplingtype != SamplingType.VECT: self.toolbar.SelectDefault() def GetMap(self): return self.map_ def OnPan(self, event): """Panning, set mouse to drag.""" self.mapWindow.SetModePan() def OnZoomIn(self, event): """Zoom in the map.""" self.mapWindow.SetModeZoomIn() def OnZoomOut(self, event): """Zoom out the map.""" self.mapWindow.SetModeZoomOut() def OnZoomToMap(self, event): layers = self.map_.GetListOfLayers() self.mapWindow.ZoomToMap(layers=layers, ignoreNulls=False, render=True) def OnDrawRadius(self, event): """Start draw mode""" self.mapWindow.mouse['use'] = "None" self.mapWindow.mouse['box'] = "line" self.mapWindow.pen = wx.Pen(colour=wx.RED, width=1, style=wx.SHORT_DASH) self.mapWindow.SetNamedCursor('cross') self.mapWindow.mouseLeftUp.connect(self._radiusDrawn) def OnDigitizeRegion(self, event): """Start draw mode""" self.mapWindow.mouse['use'] = "None" self.mapWindow.mouse['box'] = "line" self.mapWindow.pen = wx.Pen(colour=wx.RED, width=1, style=wx.SHORT_DASH) self.mapWindow.SetNamedCursor('cross') self.mapWindow.mouseLeftUp.connect(self._lineSegmentDrawn) self.mapWindow.mouseDClick.connect(self._mouseDbClick) self._registeredGraphics.GetItem(0).SetCoords([]) def OnDraw(self, event): """Start draw mode""" self.mapWindow.mouse['use'] = "None" self.mapWindow.mouse['box'] = "box" self.mapWindow.pen = wx.Pen(colour=wx.RED, width=2, style=wx.SHORT_DASH) self.mapWindow.SetNamedCursor('cross') self.mapWindow.mouseLeftUp.connect(self._rectangleDrawn) def _lineSegmentDrawn(self, x, y): item = self._registeredGraphics.GetItem(0) coords = item.GetCoords() if len(coords) == 0: coords.extend([self.mapWindow.Pixel2Cell(self.mapWindow.mouse['begin'])]) coords.extend([[x, y]]) item.SetCoords(coords) item.SetPropertyVal('hide', False) self.mapWindow.ClearLines() self._registeredGraphics.Draw(self.mapWindow.pdcTmp) def _mouseDbClick(self, x, y): item = self._registeredGraphics.GetItem(0) coords = item.GetCoords() coords.extend([[x, y]]) item.SetCoords(coords) item.SetPropertyVal('hide', False) self.mapWindow.ClearLines() self._registeredGraphics.Draw(self.mapWindow.pdc) self.createRegion() def createRegion(self): dlg = wx.TextEntryDialog(None, 'Name of sample region', 'Create region', 'region' + str(self.catId)) ret = dlg.ShowModal() while True: if ret == wx.ID_OK: raster = dlg.GetValue() if checkMapExists(raster): GMessage(parent=self, message=_("The raster file %s already" " exists, please change name") % raster) ret = dlg.ShowModal() else: dlg.Destroy() marea = self.writeArea(self._registeredGraphics.GetItem(0).GetCoords(), raster) self.nextRegion(next=True, area=marea) break else: self.nextRegion(next=False) break def nextRegion(self, next=True, area=None): self.mapWindow.ClearLines() item = self._registeredGraphics.GetItem(0) item.SetCoords([]) item.SetPropertyVal('hide', True) layers = self.map_.GetListOfLayers() self.mapWindow.ZoomToMap(layers=layers, ignoreNulls=False, render=True) if next is True: self.afterRegionDrawn.emit(marea=area) else: gcmd.GMessage(parent=self.parent, message=_("Raster map not created. Please redraw region.")) def writeArea(self, coords, rasterName): polyfile = tempfile.NamedTemporaryFile(delete=False) polyfile.write("AREA\n") for coor in coords: east, north = coor point = " %s %s\n" % (east, north) polyfile.write(point) catbuf = "=%d a\n" % self.catId polyfile.write(catbuf) self.catId = self.catId + 1 polyfile.close() region_settings = grass.parse_command('g.region', flags='p', delimiter=':') pname = polyfile.name.split('/')[-1] tmpraster = "rast_" + pname tmpvector = "vect_" + pname wx.BeginBusyCursor() wx.Yield() RunCommand('r.in.poly', input=polyfile.name, output=tmpraster, rows=region_settings['rows'], overwrite=True) RunCommand('r.to.vect', input=tmpraster, output=tmpvector, type='area', overwrite=True) RunCommand('v.to.rast', input=tmpvector, output=rasterName, value=1, use='val') wx.EndBusyCursor() grass.use_temp_region() grass.run_command('g.region', vector=tmpvector) region = grass.region() marea = MaskedArea(region, rasterName) RunCommand('g.remove', flags='f', type='raster', name=tmpraster) RunCommand('g.remove', flags='f', type='vector', name=tmpvector) os.unlink(polyfile.name) return marea def _onToolChanged(self): """Helper function to disconnect drawing""" try: self.mapWindow.mouseLeftUp.disconnect(self._rectangleDrawn) self.mapWindow.mouseLeftUp.disconnect(self._radiusDrawn) self.mapWindow.mouseMoving.disconnect(self._mouseMoving) self.mapWindow.mouseLeftDown.disconnect(self._mouseLeftDown) self.mapWindow.mouseDClick.disconnect(self._mouseDbClick) except DispatcherKeyError: pass def _radiusDrawn(self, x, y): """When drawing finished, get region values""" mouse = self.mapWindow.mouse item = self._registeredGraphics.GetItem(0) p1 = mouse['begin'] p2 = mouse['end'] dist, (north, east) = self.mapWindow.Distance(p1, p2, False) circle = Circle(p1, dist) self.mapWindow.ClearLines() self.mapWindow.pdcTmp.SetBrush(wx.Brush(wx.CYAN, wx.TRANSPARENT)) pen = wx.Pen(colour=wx.RED, width=2) self.mapWindow.pdcTmp.SetPen(pen) self.mapWindow.pdcTmp.DrawCircle(circle.point[0], circle.point[1], circle.radius) self._registeredGraphics.Draw(self.mapWindow.pdcTmp) self.createCricle(circle) def createCricle(self, c): dlg = wx.TextEntryDialog(None, 'Name of sample circle region', 'Create circle region', 'circle' + str(self.catId)) ret = dlg.ShowModal() while True: if ret == wx.ID_OK: raster = dlg.GetValue() if checkMapExists(raster): GMessage(parent=self, message=_("The raster file %s already" " exists, please change name") % raster) ret = dlg.ShowModal() else: dlg.Destroy() circle = self.writeCircle(c, raster) self.nextCircle(next=True, circle=circle) break else: self.nextCircle(next=False) break def nextCircle(self, next=True, circle=None): self.mapWindow.ClearLines() item = self._registeredGraphics.GetItem(0) item.SetPropertyVal('hide', True) layers = self.map_.GetListOfLayers() self.mapWindow.ZoomToMap(layers=layers, ignoreNulls=False, render=True) if next is True: self.afterCircleDrawn.emit(region=circle) else: gcmd.GMessage(parent=self.parent, message=_("Raster map not created. redraw region again.")) def writeCircle(self, circle, rasterName): coords = self.mapWindow.Pixel2Cell(circle.point) RunCommand('r.circle', output=rasterName, max=circle.radius, coordinate=coords, flags="b") grass.use_temp_region() grass.run_command('g.region', zoom=rasterName) region = grass.region() marea = MaskedArea(region, rasterName, circle.radius) return marea def _rectangleDrawn(self): """When drawing finished, get region values""" mouse = self.mapWindow.mouse item = self._registeredGraphics.GetItem(0) p1 = self.mapWindow.Pixel2Cell(mouse['begin']) p2 = self.mapWindow.Pixel2Cell(mouse['end']) item.SetCoords([p1, p2]) region = {'n': max(p1[1], p2[1]), 's': min(p1[1], p2[1]), 'w': min(p1[0], p2[0]), 'e': max(p1[0], p2[0])} item.SetPropertyVal('hide', False) self.mapWindow.ClearLines() self._registeredGraphics.Draw(self.mapWindow.pdcTmp) if self.samplingtype in [SamplingType.MUNITSR, SamplingType.MMVWINR]: dlg = wx.MessageDialog(self, "Is this area ok?", "select sampling unit", wx.YES_NO | wx.ICON_QUESTION) ret = dlg.ShowModal() if ret == wx.ID_YES: grass.use_temp_region() grass.run_command('g.region', n=region['n'], s=region['s'], e=region['e'], w=region['w']) tregion = grass.region() self.sampleFrameChanged.emit(region=tregion) self.mapWindow.ClearLines() item = self._registeredGraphics.GetItem(0) item.SetPropertyVal('hide', True) layers = self.map_.GetListOfLayers() self.mapWindow.ZoomToMap(layers=layers, ignoreNulls=False, render=True) else: self.nextRegion(next=False) dlg.Destroy() elif self.samplingtype != SamplingType.WHOLE: """When drawing finished, get region values""" self.sampleFrameChanged.emit(region=region)