""" MODULE: vkrige AUTHOR(S): Anne Ghisla PURPOSE: Dedicated GUI for v.krige script. DEPENDS: R 2.x, packages gstat, maptools and spgrass6, optional: automap COPYRIGHT: (C) 2009 by the GRASS Development Team This program is free software under the GNU General Public License (>=v2). Read the file COPYING that comes with GRASS for details. """ #@TODO move here imports related to wxGUI ### generic imports import os, sys from tempfile import gettempdir import time import thread ## i18N import gettext gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode = True) ### dependencies to be checked once, as they are quite time-consuming. cfr. grass.parser. # GRASS binding try: import grass.script as grass except ImportError: sys.exit(_("No GRASS-python library found.")) ### wxGUI imports GUIModulesPath = os.path.join(os.getenv("GISBASE"), "etc", "gui", "wxpython", "gui_modules") sys.path.append(GUIModulesPath) from core import globalvar import gui_core.gselect import gui_core.goutput import menuform from core.settings import UserSettings #import help import wx import wx.lib.flatnotebook as FN #import wx.lib.plot as plot # for plotting the variogram. # global variables maxint = 1e6 # instead of sys.maxint, not working with SpinCtrl on 64bit [reported by Bob Moskovitz] #@TODO move away functions not regarding the GUI class KrigingPanel(wx.Panel): """ Main panel. Contains all widgets except Menus and Statusbar. """ def __init__(self, parent, Rinstance, controller, *args, **kwargs): wx.Panel.__init__(self, parent, *args, **kwargs) self.parent = parent self.border = 4 # 1. Input data InputBoxSizer = wx.StaticBoxSizer(wx.StaticBox(self, id = wx.ID_ANY, label = _("Input Data")), orient = wx.HORIZONTAL) flexSizer = wx.FlexGridSizer(cols = 3, hgap = 5, vgap = 5) flexSizer.AddGrowableCol(1) flexSizer.Add(item = wx.StaticText(self, id = wx.ID_ANY, label = _("Point dataset:")), flag = wx.ALIGN_CENTER_VERTICAL) self.InputDataMap = gselect.VectorSelect(parent = self, ftype = 'points', updateOnPopup = False) self.InputDataMap.SetFocus() flexSizer.Add(item = self.InputDataMap, flag = wx.ALIGN_CENTER_VERTICAL) RefreshButton = wx.Button(self, id = wx.ID_REFRESH) RefreshButton.Bind(wx.EVT_BUTTON, self.OnButtonRefresh) flexSizer.Add(item = RefreshButton, flag = wx.ALIGN_CENTER_VERTICAL) flexSizer.Add(item = wx.StaticText(self, id = wx.ID_ANY, label = _("Numeric column:")), flag = wx.ALIGN_CENTER_VERTICAL) self.InputDataColumn = gselect.ColumnSelect(self, id = wx.ID_ANY) self.InputDataColumn.SetSelection(0) flexSizer.Add(item = self.InputDataColumn) self.InputDataMap.GetChildren()[0].Bind(wx.EVT_TEXT, self.OnInputDataChanged) InputBoxSizer.Add(item = flexSizer) # 2. Kriging. In book pages one for each R package. Includes variogram fit. KrigingSizer = wx.StaticBoxSizer(wx.StaticBox(self, id = wx.ID_ANY, label = _("Kriging")), wx.HORIZONTAL) self.RPackagesBook = FN.FlatNotebook(parent = self, id = wx.ID_ANY, style = FN.FNB_BOTTOM | FN.FNB_NO_NAV_BUTTONS | FN.FNB_FANCY_TABS | FN.FNB_NO_X_BUTTON) for Rpackage in ["gstat"]: # , "geoR"]: #@TODO: enable it if/when it'll be implemented. self.CreatePage(package = Rpackage, Rinstance = Rinstance, controller = controller) ## Command output. From menuform module, cmdPanel class self.goutput = goutput.GMConsole(parent = self, margin = False, notebook = self.RPackagesBook) self.goutputId = self.RPackagesBook.GetPageCount() self.outpage = self.RPackagesBook.AddPage(self.goutput, text = _("Command output")) self.RPackagesBook.SetSelection(0) KrigingSizer.Add(self.RPackagesBook, proportion = 1, flag = wx.EXPAND) # 3. Output Parameters. OutputSizer = wx.StaticBoxSizer(wx.StaticBox(self, id = wx.ID_ANY, label = _("Output")), wx.HORIZONTAL) OutputParameters = wx.GridBagSizer(hgap = 5, vgap = 5) OutputParameters.AddGrowableCol(1) OutputParameters.Add(item = wx.StaticText(self, id = wx.ID_ANY, label = _("Name for the output raster map:")), flag = wx.ALIGN_CENTER_VERTICAL, pos = (0, 0)) self.OutputMapName = gselect.Select(parent = self, id = wx.ID_ANY, type = 'raster', mapsets = [grass.gisenv()['MAPSET']]) OutputParameters.Add(item = self.OutputMapName, flag = wx.EXPAND | wx.ALL, pos = (0, 1)) self.VarianceRasterCheckbox = wx.CheckBox(self, id = wx.ID_ANY, label = _("Export variance map as well: ")) self.VarianceRasterCheckbox.SetValue(state = True) OutputParameters.Add(item = self.VarianceRasterCheckbox, flag = wx.ALIGN_CENTER_VERTICAL, pos = (1, 0)) self.OutputVarianceMapName = gselect.Select(parent = self, id = wx.ID_ANY, type = 'raster', mapsets = [grass.gisenv()['MAPSET']]) self.VarianceRasterCheckbox.Bind(wx.EVT_CHECKBOX, self.OnVarianceCBChecked) OutputParameters.Add(item = self.OutputVarianceMapName, flag = wx.EXPAND | wx.ALL, pos = (1, 1)) self.OverwriteCheckBox = wx.CheckBox(self, id = wx.ID_ANY, label = _("Allow output files to overwrite existing files")) self.OverwriteCheckBox.SetValue(UserSettings.Get(group='cmd', key='overwrite', subkey='enabled')) OutputParameters.Add(item = self.OverwriteCheckBox, pos = (2, 0), span = (1, 2)) OutputSizer.Add(OutputParameters, proportion = 0, flag = wx.EXPAND | wx.ALL, border = self.border) # 4. Run Button and Quit Button ButtonSizer = wx.BoxSizer(wx.HORIZONTAL) HelpButton = wx.Button(self, id = wx.ID_HELP) HelpButton.Bind(wx.EVT_BUTTON, self.OnHelpButton) QuitButton = wx.Button(self, id = wx.ID_EXIT) QuitButton.Bind(wx.EVT_BUTTON, self.OnCloseWindow) self.RunButton = wx.Button(self, id = wx.ID_ANY, label = _("&Run")) # no stock ID for Run button.. self.RunButton.Bind(wx.EVT_BUTTON, self.OnRunButton) self.RunButton.Enable(False) # disable it on loading the interface, as input map is not set ButtonSizer.Add(HelpButton, proportion = 0, flag = wx.ALIGN_LEFT | wx.ALL, border = self.border) ButtonSizer.Add(QuitButton, proportion = 0, flag = wx.ALIGN_RIGHT | wx.ALL, border = self.border) ButtonSizer.Add(self.RunButton, proportion = 0, flag = wx.ALIGN_RIGHT | wx.ALL, border = self.border) # Main Sizer. Add each child sizer as soon as it is ready. Sizer = wx.BoxSizer(wx.VERTICAL) Sizer.Add(InputBoxSizer, proportion = 0, flag = wx.EXPAND | wx.ALL, border = self.border) Sizer.Add(KrigingSizer, proportion = 1, flag = wx.EXPAND | wx.ALL, border = self.border) Sizer.Add(OutputSizer, proportion = 0, flag = wx.EXPAND | wx.ALL, border = self.border) Sizer.Add(ButtonSizer, proportion = 0, flag = wx.ALIGN_RIGHT | wx.ALL, border = self.border) self.SetSizerAndFit(Sizer) # last action of __init__: update imput data list. # it's performed in the few seconds gap while user examines interface before clicking anything. #@TODO: implement a splashcreen IF the maps cause a noticeable lag [markus' suggestion] self.InputDataMap.GetElementList() def CreatePage(self, package, Rinstance, controller): """ Creates the three notebook pages, one for each R package """ for package in ["gstat"]: classobj = eval("RBook"+package+"Panel") setattr(self, "RBook"+package+"Panel", (classobj(self, id = wx.ID_ANY, Rinstance = Rinstance, controller = controller))) self.RPackagesBook.AddPage(page = getattr(self, "RBook"+package+"Panel"), text = package) def OnButtonRefresh(self, event): """ Forces refresh of list of available layers. """ self.InputDataMap.GetElementList() def OnCloseWindow(self, event): """ Cancel button pressed""" self.parent.Close() event.Skip() def OnHelpButton(self, event): # file = os.path.join(os.getenv("GISBASE"), "docs", "html", "v.krige.html") # file = os.path.join(os.path.curdir, "description.html") # @TODO fix HelpWindow # helpFrame = help.HelpWindow(parent=self, id=wx.ID_ANY, # title=_("GRASS - Help page for v.krige"), # size=(640, 480), # file=file) # helpFrame.Show(True) grass.run_command('g.manual', entry = 'v.krige') event.Skip() def OnInputDataChanged(self, event): """ Refreshes list of columns and fills output map name TextCtrl """ MapName = event.GetString() self.InputDataColumn.InsertColumns(vector = MapName, layer = 1, excludeKey = True, type = ['integer', 'double precision']) self.InputDataColumn.SetSelection(0) self.RunButton.Enable(self.InputDataColumn.GetSelection() is not -1) self.RBookgstatPanel.PlotButton.Enable(self.InputDataColumn.GetSelection() is not -1) if self.InputDataColumn.GetSelection() is not -1: self.OutputMapName.SetValue(MapName.split("@")[0]+"_kriging") self.OutputVarianceMapName.SetValue(MapName.split("@")[0]+"_kriging_var") else: self.OutputMapName.SetValue('') self.OutputVarianceMapName.SetValue('') def OnRunButton(self,event): """ Execute R analysis. """ #@FIXME: send data to main method instead of running it here. #-1: get the selected notebook page. The user shall know that [s]he can modify settings in all # pages, but only the selected one will be executed when Run is pressed. SelectedPanel = self.RPackagesBook.GetCurrentPage() if self.RPackagesBook.GetPageText(self.RPackagesBook.GetSelection()) == 'Command output': self.goutput.WriteError("No parameters for running. Please select \"gstat\" tab, check parameters and re-run.") return False # no break invoked by above function # mount command string as it would have been written on CLI command = ["v.krige", "input=" + self.InputDataMap.GetValue(), "column=" + self.InputDataColumn.GetValue(), "output=" + self.OutputMapName.GetValue(), "package=" + '%s' % self.RPackagesBook.GetPageText(self.RPackagesBook.GetSelection())] if not hasattr(SelectedPanel, 'VariogramCheckBox') or not SelectedPanel.VariogramCheckBox.IsChecked(): command.append("model=" + '%s' % SelectedPanel.ModelChoicebox.GetStringSelection().split(" ")[0]) for i in ['Sill', 'Nugget', 'Range']: if getattr(SelectedPanel, i+"ChextBox").IsChecked(): command.append(i.lower() + "=" + '%s' % getattr(SelectedPanel, i+'Ctrl').GetValue()) if SelectedPanel.KrigingRadioBox.GetStringSelection() == "Block kriging": command.append("block=" + '%s' % SelectedPanel.BlockSpinBox.GetValue()) if self.OverwriteCheckBox.IsChecked(): command.append("--overwrite") if self.VarianceRasterCheckbox.IsChecked(): command.append("output_var=" + self.OutputVarianceMapName.GetValue()) # give it to the output console #@FIXME: it runs the command as a NEW instance. Reimports data, recalculates variogram fit.. #otherwise I can use Controller() and mimic RunCmd behaviour. self.goutput.RunCmd(command, switchPage = True) def OnVarianceCBChecked(self, event): self.OutputVarianceMapName.Enable(event.IsChecked()) class KrigingModule(wx.Frame): """ Kriging module for GRASS GIS. Depends on R and its packages gstat and geoR. """ def __init__(self, parent, Rinstance, controller, *args, **kwargs): wx.Frame.__init__(self, parent, *args, **kwargs) # setting properties and all widgettery self.SetTitle(_("Kriging Module")) self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass_dialog.ico'), wx.BITMAP_TYPE_ICO)) self.log = Log(self) self.CreateStatusBar() self.log.message(_("Ready.")) self.Panel = KrigingPanel(self, Rinstance, controller) self.SetMinSize(self.GetBestSize()) self.SetSize(self.GetBestSize()) class Log: """ The log output is redirected to the status bar of the containing frame. """ def __init__(self, parent): self.parent = parent def message(self, text_string): """ Updates status bar """ self.parent.SetStatusText(text_string.strip()) class RBookPanel(wx.Panel): """ Generic notebook page with shared widgets and empty kriging functions. """ def __init__(self, parent, *args, **kwargs): wx.Panel.__init__(self, parent, *args, **kwargs) self.parent = parent self.VariogramSizer = wx.StaticBoxSizer(wx.StaticBox(self, id = wx.ID_ANY, label = _("Variogram fitting")), wx.HORIZONTAL) self.LeftSizer = wx.BoxSizer(wx.VERTICAL) self.RightSizer = wx.BoxSizer(wx.VERTICAL) self.ParametersSizer = wx.GridBagSizer(vgap = 5, hgap = 5) self.VariogramSizer.Add(self.LeftSizer, proportion = 0, flag = wx.EXPAND | wx.ALL, border = parent.border) self.VariogramSizer.Add(self.RightSizer, proportion = 0, flag = wx.EXPAND | wx.ALL, border = parent.border) # left side of Variogram fitting. The checkboxes and spinctrls. self.PlotButton = wx.Button(self, id = wx.ID_ANY, label = _("Plot/refresh variogram")) # no stock ID for Run button.. self.PlotButton.Bind(wx.EVT_BUTTON, self.OnPlotButton) self.PlotButton.Enable(False) # grey it out until a suitable layer is available self.LeftSizer.Add(self.PlotButton, proportion = 0, flag = wx.ALL, border = parent.border) self.LeftSizer.Add(self.ParametersSizer, proportion = 0, flag = wx.EXPAND | wx.ALL, border = parent.border) self.ParametersList = ["Sill", "Nugget", "Range"] MinValues = [0,0,1] for n in self.ParametersList: setattr(self, n+"ChextBox", wx.CheckBox(self, id = self.ParametersList.index(n), label = _(n + ":"))) setattr(self, n+"Ctrl", (wx.SpinCtrl(self, id = wx.ID_ANY, min = MinValues[self.ParametersList.index(n)], max = maxint))) getattr(self, n+"ChextBox").Bind(wx.EVT_CHECKBOX, self.UseValue, id = self.ParametersList.index(n)) setattr(self, n+"Sizer", (wx.BoxSizer(wx.HORIZONTAL))) self.ParametersSizer.Add(getattr(self, n+"ChextBox"), flag = wx.ALIGN_CENTER_VERTICAL, pos = (self.ParametersList.index(n),0)) self.ParametersSizer.Add(getattr(self, n+"Ctrl"), flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL, pos = (self.ParametersList.index(n),1)) # right side of the Variogram fitting. The plot area. #Plot = wx.StaticText(self, id= wx.ID_ANY, label = "Check Plot Variogram to interactively fit model.") #PlotPanel = wx.Panel(self) #self.PlotArea = plot.PlotCanvas(PlotPanel) #self.PlotArea.SetInitialSize(size = (250,250)) #self.RightSizer.Add(PlotPanel, proportion=0, flag= wx.EXPAND|wx.ALL, border=parent.border) self.KrigingSizer = wx.StaticBoxSizer(wx.StaticBox(self, id = wx.ID_ANY, label = _("Kriging techniques")), wx.VERTICAL) KrigingList = ["Ordinary kriging", "Block kriging"]#, "Universal kriging"] #@FIXME: i18n on the list? self.KrigingRadioBox = wx.RadioBox(self, id = wx.ID_ANY, choices = KrigingList, majorDimension = 1, style = wx.RA_SPECIFY_COLS) self.KrigingRadioBox.Bind(wx.EVT_RADIOBOX, self.HideBlockOptions) self.KrigingSizer.Add(self.KrigingRadioBox, proportion = 0, flag = wx.EXPAND | wx.ALL, border = parent.border) # block kriging parameters. Size. BlockSizer = wx.BoxSizer(wx.HORIZONTAL) BlockLabel = wx.StaticText(self, id = wx.ID_ANY, label = _("Block size:")) self.BlockSpinBox = wx.SpinCtrl(self, id = wx.ID_ANY, min = 1, max = maxint) self.BlockSpinBox.Enable(False) # default choice is Ordinary kriging so block param is disabled BlockSizer.Add(BlockLabel, flag = wx.ALIGN_CENTER_VERTICAL | wx.ALL, border = parent.border) BlockSizer.Add(self.BlockSpinBox, flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border = parent.border) self.KrigingSizer.Add(BlockSizer, flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border = parent.border) self.Sizer = wx.BoxSizer(wx.VERTICAL) self.Sizer.Add(self.VariogramSizer, proportion = 0, flag = wx.EXPAND | wx.ALL, border = parent.border) self.Sizer.Add(self.KrigingSizer, proportion = 0, flag = wx.EXPAND | wx.ALL, border = parent.border) def HideBlockOptions(self, event): self.BlockSpinBox.Enable(event.GetInt() == 1) def OnPlotButton(self,event): """ Plots variogram with current options. """ pass def UseValue(self, event): """ Enables/Disables the SpinCtrl in respect of the checkbox. """ n = self.ParametersList[event.GetId()] getattr(self, n+"Ctrl").Enable(event.IsChecked()) class RBookgstatPanel(RBookPanel): """ Subclass of RBookPanel, with specific gstat options and kriging functions. """ def __init__(self, parent, Rinstance, controller, *args, **kwargs): RBookPanel.__init__(self, parent, *args, **kwargs) # assigns Rinstance, that comes from the GUI call of v.krige.py. robjects = Rinstance self.controller = controller if robjects.r.require('automap')[0]: self.VariogramCheckBox = wx.CheckBox(self, id = wx.ID_ANY, label = _("Auto-fit variogram")) self.LeftSizer.Insert(0, self.VariogramCheckBox, proportion = 0, flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border = 4) self.SetSizerAndFit(self.Sizer) self.VariogramCheckBox.Bind(wx.EVT_CHECKBOX, self.HideOptions) self.VariogramCheckBox.SetValue(state = True) # check it by default ModelFactor = robjects.r.vgm().rx('long') ModelList = robjects.r.levels(ModelFactor[0]) #@FIXME: no other way to let the Python pick it up.. # and this is te wrong place where to load this list. should be at the very beginning. self.ModelChoicebox = wx.Choice(self, id = wx.ID_ANY, choices = ModelList) # disable model parameters' widgets by default for n in ["Sill", "Nugget", "Range"]: getattr(self, n+"Ctrl").Enable(False) self.ModelChoicebox.Enable(False) VariogramSubSizer = wx.BoxSizer(wx.HORIZONTAL) VariogramSubSizer.Add(item = wx.StaticText(self, id = wx.ID_ANY, label = _("Model: ")), flag = wx.ALIGN_CENTER_VERTICAL | wx.ALL, border = 4) VariogramSubSizer.Add(item = self.ModelChoicebox, flag = wx.ALIGN_CENTER_VERTICAL | wx.ALL, border = 4) self.LeftSizer.Insert(2, item = VariogramSubSizer) self.SetSizerAndFit(self.Sizer) def HideOptions(self, event): self.ModelChoicebox.Enable(not event.IsChecked()) for n in ["Sill", "Nugget", "Range"]: if not event.IsChecked(): getattr(self, n+"Ctrl").Enable(True) getattr(self, n+ "ChextBox").SetValue(True) getattr(self, n+ "ChextBox").Enable(False) # grey it out keeping it checked.. improvable else: getattr(self, n+"Ctrl").Enable(False) getattr(self, n+ "ChextBox").SetValue(False) getattr(self, n+ "ChextBox").Enable(True) #@FIXME: was for n in self.ParametersSizer.GetChildren(): n.Enable(False) but doesn't work def OnPlotButton(self,event): """ Plots variogram with current options. """ ## BIG WARNING: smell of code duplication. Fix this asap. emminchia! #controller = Controller() # sed, if needed, #controller = self.controller map = self.parent.InputDataMap.GetValue() column = self.parent.InputDataColumn.GetValue() # import data or pick them up if globals()["InputData"] is None: globals()["InputData"] = controller.ImportMap(map = map, column = column) # fit the variogram or pick it up Formula = controller.ComposeFormula(column = column, isblock = self.KrigingRadioBox.GetStringSelection() == "Block kriging", inputdata = globals()['InputData']) #if globals()["Variogram"] is None: if hasattr(self, 'VariogramCheckBox') and self.VariogramCheckBox.IsChecked(): self.model = '' for each in ("Sill","Nugget","Range"): if getattr(self, each+'ChextBox').IsChecked(): setattr(self, each.lower(), getattr(self, each+"Ctrl").GetValue()) else: setattr(self, each.lower(), robjects.r('''NA''')) else: self.model = self.ModelChoicebox.GetStringSelection().split(" ")[0] for each in ("Sill","Nugget","Range"): if getattr(self, each+'ChextBox').IsChecked(): #@FIXME will be removed when chextboxes will be frozen setattr(self, each.lower(), getattr(self, each+"Ctrl").GetValue()) globals()["Variogram"] = controller.FitVariogram(Formula, InputData, model = self.model, sill = self.sill, nugget = self.nugget, range = self.range) # use R plot function, in a separate window. thread.start_new_thread(self.plot, ()) def plot(self): #robjects.r.X11() #robjects.r.png("variogram.png") textplot = robjects.r.plot(Variogram['datavariogram'], Variogram['variogrammodel']) print textplot self.refresh() #robjects.r['dev.off']() def refresh(self): while True: rinterface.process_revents() time.sleep(0.1) class RBookgeoRPanel(RBookPanel): """ Subclass of RBookPanel, with specific geoR options and kriging functions. """ def __init__(self, parent, *args, **kwargs): RBookPanel.__init__(self, parent, *args, **kwargs) #@TODO: change these two lines as soon as geoR f(x)s are integrated. for n in self.GetChildren(): n.Hide() self.Sizer.Add(wx.StaticText(self, id = wx.ID_ANY, label = _("Work in progress! No functionality provided."))) self.SetSizerAndFit(self.Sizer)