12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631 |
- """
- @package iclass.frame
- @brief wxIClass frame with toolbar for digitizing training areas and
- for spectral signature analysis.
- Classes:
- - frame::IClassMapPanel
- - frame::IClassMapDisplay
- - frame::MapManager
- (C) 2006-2013 by the GRASS Development Team
- This program is free software under the GNU General Public
- License (>=v2). Read the file COPYING that comes with GRASS
- for details.
- @author Vaclav Petras <wenzeslaus gmail.com>
- @author Anna Kratochvilova <kratochanna gmail.com>
- """
- import os
- import six
- import copy
- import tempfile
- import wx
- from ctypes import *
- try:
- from grass.lib.imagery import *
- from grass.lib.vector import *
- haveIClass = True
- errMsg = ""
- except ImportError as e:
- haveIClass = False
- errMsg = _("Loading imagery lib failed.\n%s") % e
- import grass.script as grass
- from mapdisp import statusbar as sb
- from mapdisp.main import StandaloneMapDisplayGrassInterface
- from mapwin.buffered import BufferedMapWindow
- from vdigit.toolbars import VDigitToolbar
- from gui_core.mapdisp import DoubleMapPanel, FrameMixin
- from core import globalvar
- from core.render import Map
- from core.gcmd import RunCommand, GMessage, GError
- from gui_core.dialogs import SetOpacityDialog
- from gui_core.wrap import Menu
- from dbmgr.vinfo import VectorDBInfo
- from iclass.digit import IClassVDigitWindow, IClassVDigit
- from iclass.toolbars import (
- IClassMapToolbar,
- IClassMiscToolbar,
- IClassToolbar,
- IClassMapManagerToolbar,
- )
- from iclass.statistics import StatisticsData
- from iclass.dialogs import (
- IClassCategoryManagerDialog,
- IClassGroupDialog,
- IClassSignatureFileDialog,
- IClassExportAreasDialog,
- IClassMapDialog,
- )
- from iclass.plots import PlotPanel
- from grass.pydispatch.signal import Signal
- class IClassMapPanel(DoubleMapPanel):
- """wxIClass main frame
- It has two map windows one for digitizing training areas and one for
- result preview.
- It generates histograms, raster maps and signature files using
- @c I_iclass_* functions from C imagery library.
- It is wxGUI counterpart of old i.class module.
- """
- def __init__(
- self,
- parent=None,
- giface=None,
- title=_("Supervised Classification Tool"),
- toolbars=["iClassMisc", "iClassMap", "vdigit", "iClass"],
- size=(875, 600),
- name="IClassWindow",
- **kwargs,
- ):
- """
- :param parent: (no parent is expected)
- :param title: window title
- :param toolbars: dictionary of active toolbars (default value represents all toolbars)
- :param size: default size
- """
- DoubleMapPanel.__init__(
- self,
- parent=parent,
- title=title,
- name=name,
- firstMap=Map(),
- secondMap=Map(),
- **kwargs,
- )
- if giface:
- self.giface = giface
- else:
- self.giface = StandaloneMapDisplayGrassInterface(self)
- self.tree = None
- # show computation region by defaut
- self.mapWindowProperties.showRegion = True
- self.firstMapWindow = IClassVDigitWindow(
- parent=self,
- giface=self.giface,
- properties=self.mapWindowProperties,
- map=self.firstMap,
- )
- self.secondMapWindow = BufferedMapWindow(
- parent=self,
- giface=self.giface,
- properties=self.mapWindowProperties,
- Map=self.secondMap,
- )
- self.MapWindow = self.firstMapWindow # current by default
- self._bindWindowsActivation()
- self._setUpMapWindow(self.firstMapWindow)
- self._setUpMapWindow(self.secondMapWindow)
- self.firstMapWindow.InitZoomHistory()
- self.secondMapWindow.InitZoomHistory()
- # TODO: for vdigit: it does nothing here because areas do not produce
- # this info
- self.firstMapWindow.digitizingInfo.connect(
- lambda text: self.statusbarManager.statusbarItems[
- "coordinates"
- ].SetAdditionalInfo(text)
- )
- self.firstMapWindow.digitizingInfoUnavailable.connect(
- lambda: self.statusbarManager.statusbarItems[
- "coordinates"
- ].SetAdditionalInfo(None)
- )
- self.SetSize(size)
- #
- # Signals
- #
- self.groupSet = Signal("IClassMapPanel.groupSet")
- self.categoryChanged = Signal("IClassMapPanel.categoryChanged")
- self.InitStatistics()
- #
- # Add toolbars
- #
- for toolb in toolbars:
- self.AddToolbar(toolb)
- self.firstMapWindow.SetToolbar(self.toolbars["vdigit"])
- self.GetMapToolbar().GetActiveMapTool().Bind(wx.EVT_CHOICE, self.OnUpdateActive)
- self.trainingMapManager = MapManager(
- self, mapWindow=self.GetFirstWindow(), Map=self.GetFirstMap()
- )
- self.previewMapManager = MapManager(
- self, mapWindow=self.GetSecondWindow(), Map=self.GetSecondMap()
- )
- self.changes = False
- self.exportVector = None
- # dialogs
- self.dialogs = dict()
- self.dialogs["classManager"] = None
- self.dialogs["scatt_plot"] = None
- # just to make digitizer happy
- self.dialogs["attributes"] = None
- self.dialogs["category"] = None
- # PyPlot init
- self.plotPanel = PlotPanel(self, giface=self.giface, stats_data=self.stats_data)
- # statusbar items
- statusbarItems = [
- sb.SbCoordinates,
- sb.SbRegionExtent,
- sb.SbCompRegionExtent,
- sb.SbDisplayGeometry,
- sb.SbMapScale,
- sb.SbGoTo,
- ]
- self.statusbar = self.CreateStatusbar(statusbarItems)
- self._addPanes()
- self._mgr.Update()
- self.trainingMapManager.SetToolbar(self.toolbars["iClassTrainingMapManager"])
- self.previewMapManager.SetToolbar(self.toolbars["iClassPreviewMapManager"])
- # default action
- self.GetMapToolbar().SelectDefault()
- wx.CallAfter(self.AddTrainingAreaMap)
- self.Bind(wx.EVT_SIZE, self.OnSize)
- self.SendSizeEvent()
- def OnCloseWindow(self, event):
- self.GetFirstWindow().GetDigit().CloseMap()
- self.plotPanel.CloseWindow()
- self._cleanup()
- self._mgr.UnInit()
- self.Destroy()
- def _cleanup(self):
- """Frees C structs and removes vector map and all raster maps."""
- I_free_signatures(self.signatures)
- I_free_group_ref(self.refer)
- for st in self.cStatisticsDict.values():
- I_iclass_free_statistics(st)
- self.RemoveTempVector()
- for i in self.stats_data.GetCategories():
- self.RemoveTempRaster(self.stats_data.GetStatistics(i).rasterName)
- def OnHelp(self, event):
- """Show help page"""
- self.giface.Help(entry="wxGUI.iclass")
- def _getTempVectorName(self):
- """Return new name for temporary vector map (training areas)"""
- vectorPath = grass.tempfile(create=False)
- return "trAreas" + os.path.basename(vectorPath).replace(".", "")
- def SetGroup(self, group, subgroup):
- """Set group and subgroup manually"""
- self.g = {"group": group, "subgroup": subgroup}
- def CreateTempVector(self):
- """Create temporary vector map for training areas"""
- vectorName = self._getTempVectorName()
- env = os.environ.copy()
- env["GRASS_VECTOR_TEMPORARY"] = "1" # create temporary map
- cmd = ("v.edit", {"tool": "create", "map": vectorName})
- ret = RunCommand(prog=cmd[0], parent=self, env=env, **cmd[1])
- if ret != 0:
- return False
- return vectorName
- def RemoveTempVector(self):
- """Removes temporary vector map with training areas"""
- ret = RunCommand(
- prog="g.remove",
- parent=self,
- flags="f",
- type="vector",
- name=self.trainingAreaVector,
- )
- if ret != 0:
- return False
- return True
- def RemoveTempRaster(self, raster):
- """Removes temporary raster maps"""
- self.GetFirstMap().Clean()
- self.GetSecondMap().Clean()
- ret = RunCommand(
- prog="g.remove", parent=self, flags="f", type="raster", name=raster
- )
- if ret != 0:
- return False
- return True
- def AddToolbar(self, name):
- """Add defined toolbar to the window
- Currently known toolbars are:
- - 'iClassMap' - basic map toolbar
- - 'iClass' - iclass tools
- - 'iClassMisc' - miscellaneous (help)
- - 'vdigit' - digitizer toolbar (areas)
- Toolbars 'iClassPreviewMapManager' are added in _addPanes().
- """
- if name == "iClassMap":
- if "iClassMap" not in self.toolbars:
- self.toolbars[name] = IClassMapToolbar(self, self._toolSwitcher)
- self._mgr.AddPane(
- self.toolbars[name],
- wx.aui.AuiPaneInfo()
- .Name(name)
- .Caption(_("Map Toolbar"))
- .ToolbarPane()
- .Top()
- .LeftDockable(False)
- .RightDockable(False)
- .BottomDockable(False)
- .TopDockable(True)
- .CloseButton(False)
- .Layer(2)
- .Row(1)
- .Position(0)
- .BestSize((self.toolbars[name].GetBestSize())),
- )
- if name == "iClass":
- if "iClass" not in self.toolbars:
- self.toolbars[name] = IClassToolbar(self, stats_data=self.stats_data)
- self._mgr.AddPane(
- self.toolbars[name],
- wx.aui.AuiPaneInfo()
- .Name(name)
- .Caption(_("IClass Toolbar"))
- .ToolbarPane()
- .Top()
- .LeftDockable(False)
- .RightDockable(False)
- .BottomDockable(False)
- .TopDockable(True)
- .CloseButton(False)
- .Layer(2)
- .Row(2)
- .Position(0)
- .BestSize((self.toolbars[name].GetBestSize())),
- )
- if name == "iClassMisc":
- if "iClassMisc" not in self.toolbars:
- self.toolbars[name] = IClassMiscToolbar(self)
- self._mgr.AddPane(
- self.toolbars[name],
- wx.aui.AuiPaneInfo()
- .Name(name)
- .Caption(_("IClass Misc Toolbar"))
- .ToolbarPane()
- .Top()
- .LeftDockable(False)
- .RightDockable(False)
- .BottomDockable(False)
- .TopDockable(True)
- .CloseButton(False)
- .Layer(2)
- .Row(1)
- .Position(1)
- .BestSize((self.toolbars[name].GetBestSize())),
- )
- if name == "vdigit":
- if "vdigit" not in self.toolbars:
- self.toolbars[name] = VDigitToolbar(
- parent=self,
- toolSwitcher=self._toolSwitcher,
- MapWindow=self.GetFirstWindow(),
- digitClass=IClassVDigit,
- giface=self.giface,
- tools=[
- "addArea",
- "moveVertex",
- "addVertex",
- "removeVertex",
- "editLine",
- "moveLine",
- "deleteArea",
- "undo",
- "redo",
- "settings",
- ],
- )
- self._mgr.AddPane(
- self.toolbars[name],
- wx.aui.AuiPaneInfo()
- .Name(name)
- .Caption(_("Digitization Toolbar"))
- .ToolbarPane()
- .Top()
- .LeftDockable(False)
- .RightDockable(False)
- .BottomDockable(False)
- .TopDockable(True)
- .CloseButton(False)
- .Layer(2)
- .Row(2)
- .Position(1)
- .BestSize((self.toolbars[name].GetBestSize())),
- )
- self._mgr.Update()
- def _addPanes(self):
- """Add mapwindows, toolbars and statusbar to aui manager"""
- self._addPaneMapWindow(name="training", position=0)
- self._addPaneToolbar(name="iClassTrainingMapManager", position=1)
- self._addPaneMapWindow(name="preview", position=2)
- self._addPaneToolbar(name="iClassPreviewMapManager", position=3)
- # otherwise best size was ignored
- self._mgr.SetDockSizeConstraint(0.5, 0.5)
- self._mgr.AddPane(
- self.plotPanel,
- wx.aui.AuiPaneInfo()
- .Name("plots")
- .Caption(_("Plots"))
- .Dockable(False)
- .Floatable(False)
- .CloseButton(False)
- .Left()
- .Layer(1)
- .BestSize((335, -1)),
- )
- # statusbar
- self.AddStatusbarPane()
- def _addPaneToolbar(self, name, position):
- if name == "iClassPreviewMapManager":
- parent = self.previewMapManager
- else:
- parent = self.trainingMapManager
- self.toolbars[name] = IClassMapManagerToolbar(self, parent)
- self._mgr.AddPane(
- self.toolbars[name],
- wx.aui.AuiPaneInfo()
- .ToolbarPane()
- .Movable()
- .Name(name)
- .CloseButton(False)
- .Center()
- .Layer(0)
- .Position(position)
- .BestSize((self.toolbars[name].GetBestSize())),
- )
- def _addPaneMapWindow(self, name, position):
- if name == "preview":
- window = self.GetSecondWindow()
- caption = _("Preview Display")
- else:
- window = self.GetFirstWindow()
- caption = _("Training Areas Display")
- self._mgr.AddPane(
- window,
- wx.aui.AuiPaneInfo()
- .Name(name)
- .Caption(caption)
- .Dockable(False)
- .Floatable(False)
- .CloseButton(False)
- .Center()
- .Layer(0)
- .Position(position),
- )
- def OnUpdateActive(self, event):
- """
- .. todo::
- move to DoubleMapPanel?
- """
- if self.GetMapToolbar().GetActiveMap() == 0:
- self.MapWindow = self.firstMapWindow
- self.Map = self.firstMap
- else:
- self.MapWindow = self.secondMapWindow
- self.Map = self.secondMap
- self.UpdateActive(self.MapWindow)
- # for wingrass
- if os.name == "nt":
- self.MapWindow.SetFocus()
- def UpdateActive(self, win):
- """
- .. todo::
- move to DoubleMapPanel?
- """
- mapTb = self.GetMapToolbar()
- # optionally disable tool zoomback tool
- mapTb.Enable("zoomBack", enable=(len(self.MapWindow.zoomhistory) > 1))
- if mapTb.GetActiveMap() != (win == self.secondMapWindow):
- mapTb.SetActiveMap((win == self.secondMapWindow))
- self.StatusbarUpdate()
- def ActivateFirstMap(self, event=None):
- DoubleMapPanel.ActivateFirstMap(self, event)
- self.GetMapToolbar().Enable(
- "zoomBack", enable=(len(self.MapWindow.zoomhistory) > 1)
- )
- def ActivateSecondMap(self, event=None):
- DoubleMapPanel.ActivateSecondMap(self, event)
- self.GetMapToolbar().Enable(
- "zoomBack", enable=(len(self.MapWindow.zoomhistory) > 1)
- )
- def GetMapToolbar(self):
- """Returns toolbar with zooming tools"""
- return self.toolbars["iClassMap"] if "iClassMap" in self.toolbars else None
- def GetClassColor(self, cat):
- """Get class color as string
- :param cat: class category
- :return: 'R:G:B'
- """
- if cat in self.stats_data.GetCategories():
- return self.stats_data.GetStatistics(cat).color
- return "0:0:0"
- def OnZoomMenu(self, event):
- """Popup Zoom menu"""
- zoommenu = Menu()
- # Add items to the menu
- i = 0
- for label, handler in (
- (
- _("Adjust Training Area Display to Preview Display"),
- self.OnZoomToPreview,
- ),
- (
- _("Adjust Preview display to Training Area Display"),
- self.OnZoomToTraining,
- ),
- (_("Display synchronization ON"), lambda event: self.SetBindRegions(True)),
- (
- _("Display synchronization OFF"),
- lambda event: self.SetBindRegions(False),
- ),
- ):
- if label is None:
- zoommenu.AppendSeparator()
- continue
- item = wx.MenuItem(zoommenu, wx.ID_ANY, label)
- zoommenu.AppendItem(item)
- self.Bind(wx.EVT_MENU, handler, item)
- if i == 3:
- item.Enable(not self._bindRegions)
- elif i == 4:
- item.Enable(self._bindRegions)
- i += 1
- # Popup the menu. If an item is selected then its handler
- # will be called before PopupMenu returns.
- self.PopupMenu(zoommenu)
- zoommenu.Destroy()
- def OnZoomToTraining(self, event):
- """Set preview display to match extents of training display"""
- if not self.MapWindow == self.GetSecondWindow():
- self.MapWindow = self.GetSecondWindow()
- self.Map = self.GetSecondMap()
- self.UpdateActive(self.GetSecondWindow())
- newreg = self.firstMap.GetCurrentRegion()
- self.GetSecondMap().region = copy.copy(newreg)
- self.Render(self.GetSecondWindow())
- def OnZoomToPreview(self, event):
- """Set preview display to match extents of training display"""
- if not self.MapWindow == self.GetFirstWindow():
- self.MapWindow = self.GetFirstWindow()
- self.Map = self.GetFirstMap()
- self.UpdateActive(self.GetFirstWindow())
- newreg = self.GetSecondMap().GetCurrentRegion()
- self.GetFirstMap().region = copy.copy(newreg)
- self.Render(self.GetFirstWindow())
- def AddBands(self):
- """Add imagery group"""
- dlg = IClassGroupDialog(self, group=self.g["group"])
- while True:
- if dlg.ShowModal() == wx.ID_OK:
- if dlg.GetGroupBandsErr(parent=self):
- g, s = dlg.GetData()
- group = grass.find_file(name=g, element="group")
- self.g["group"] = group["name"]
- self.g["subgroup"] = s
- self.groupSet.emit(
- group=self.g["group"], subgroup=self.g["subgroup"]
- )
- break
- else:
- break
- dlg.Destroy()
- def OnImportAreas(self, event):
- """Import training areas"""
- # check if we have any changes
- if self.GetAreasCount() or self.stats_data.GetCategories():
- qdlg = wx.MessageDialog(
- parent=self,
- message=_("All changes will be lost. " "Do you want to continue?"),
- style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION | wx.CENTRE,
- )
- if qdlg.ShowModal() == wx.ID_NO:
- qdlg.Destroy()
- return
- qdlg.Destroy()
- dlg = IClassMapDialog(self, title=_("Import vector map"), element="vector")
- if dlg.ShowModal() == wx.ID_OK:
- vName = dlg.GetMap()
- self.ImportAreas(vName)
- dlg.Destroy()
- def _checkImportedTopo(self, vector):
- """Check if imported vector map has areas
- :param str vector: vector map name
- :return: warning message (empty if topology is ok)
- """
- topo = grass.vector_info_topo(map=vector)
- warning = ""
- if topo["areas"] == 0:
- warning = _("No areas in vector map <%s>.\n" % vector)
- if topo["points"] or topo["lines"]:
- warning += _(
- "Vector map <%s> contains points or lines, "
- "these features are ignored." % vector
- )
- return warning
- def ImportAreas(self, vector):
- """Import training areas.
- If table connected, try load certain columns to class manager
- :param str vector: vector map name
- """
- warning = self._checkImportedTopo(vector)
- if warning:
- GMessage(parent=self, message=warning)
- return
- wx.BeginBusyCursor()
- wx.GetApp().Yield()
- # close, build, copy and open again the temporary vector
- digitClass = self.GetFirstWindow().GetDigit()
- # open vector map to be imported
- if digitClass.OpenMap(vector, update=False) is None:
- GError(parent=self, message=_("Unable to open vector map <%s>") % vector)
- return
- # copy features to the temporary map
- vname = self._getTempVectorName()
- # avoid deleting temporary map
- os.environ["GRASS_VECTOR_TEMPORARY"] = "1"
- if digitClass.CopyMap(vname, tmp=True, update=True) == -1:
- GError(
- parent=self,
- message=_("Unable to copy vector features from <%s>") % vector,
- )
- return
- del os.environ["GRASS_VECTOR_TEMPORARY"]
- # close map
- digitClass.CloseMap()
- # open temporary map (copy of imported map)
- self.poMapInfo = digitClass.OpenMap(vname, tmp=True)
- if self.poMapInfo is None:
- GError(parent=self, message=_("Unable to open temporary vector map"))
- return
- # remove temporary rasters
- for cat in self.stats_data.GetCategories():
- self.RemoveTempRaster(self.stats_data.GetStatistics(cat).rasterName)
- # clear current statistics
- self.stats_data.DeleteAllStatistics()
- # reset plots
- self.plotPanel.Reset()
- self.GetFirstWindow().UpdateMap(render=False, renderVector=True)
- self.ImportClasses(vector)
- # should be saved in attribute table?
- self.toolbars["iClass"].UpdateStddev(1.5)
- wx.EndBusyCursor()
- return True
- def ImportClasses(self, vector):
- """If imported map has table, try to import certain columns to class manager"""
- # check connection
- dbInfo = VectorDBInfo(vector)
- connected = len(dbInfo.layers.keys()) > 0
- # remove attribute table of temporary vector, we don't need it
- if connected:
- RunCommand("v.db.droptable", flags="f", map=self.trainingAreaVector)
- # we use first layer with table, TODO: user should choose
- layer = None
- for key in dbInfo.layers.keys():
- if dbInfo.GetTable(key):
- layer = key
- # get columns to check if we can use them
- # TODO: let user choose which columns mean what
- if layer is not None:
- columns = dbInfo.GetColumns(table=dbInfo.GetTable(layer))
- else:
- columns = []
- # get class manager
- if self.dialogs["classManager"] is None:
- self.dialogs["classManager"] = IClassCategoryManagerDialog(self)
- listCtrl = self.dialogs["classManager"].GetListCtrl()
- # unable to load data (no connection, table, right columns)
- if (
- not connected
- or layer is None
- or "class" not in columns
- or "color" not in columns
- ):
- # no table connected
- cats = RunCommand(
- "v.category",
- input=vector,
- layer=1, # set layer?
- # type = ['centroid', 'area'] ?
- option="print",
- read=True,
- )
- cats = map(int, cats.strip().split())
- cats = sorted(list(set(cats)))
- for cat in cats:
- listCtrl.AddCategory(cat=cat, name="class_%d" % cat, color="0:0:0")
- # connection, table and columns exists
- else:
- columns = ["cat", "class", "color"]
- ret = RunCommand(
- "v.db.select",
- quiet=True,
- parent=self,
- flags="c",
- map=vector,
- layer=1,
- columns=",".join(columns),
- read=True,
- )
- records = ret.strip().splitlines()
- for record in records:
- record = record.split("|")
- listCtrl.AddCategory(
- cat=int(record[0]), name=record[1], color=record[2]
- )
- def OnExportAreas(self, event):
- """Export training areas"""
- if self.GetAreasCount() == 0:
- GMessage(parent=self, message=_("No training areas to export."))
- return
- dlg = IClassExportAreasDialog(self, vectorName=self.exportVector)
- if dlg.ShowModal() == wx.ID_OK:
- vName = dlg.GetVectorName()
- self.exportVector = vName
- withTable = dlg.WithTable()
- dlg.Destroy()
- if self.ExportAreas(vectorName=vName, withTable=withTable):
- GMessage(
- _("%d training areas (%d classes) exported to vector map <%s>.")
- % (
- self.GetAreasCount(),
- len(self.stats_data.GetCategories()),
- self.exportVector,
- ),
- parent=self,
- )
- def ExportAreas(self, vectorName, withTable):
- """Export training areas to new vector map (with attribute table).
- :param str vectorName: name of exported vector map
- :param bool withTable: true if attribute table is required
- """
- wx.BeginBusyCursor()
- wx.GetApp().Yield()
- # close, build, copy and open again the temporary vector
- digitClass = self.GetFirstWindow().GetDigit()
- if "@" in vectorName:
- vectorName = vectorName.split("@")[0]
- if digitClass.CopyMap(vectorName) < 0:
- return False
- if not withTable:
- wx.EndBusyCursor()
- return False
- # add new table
- columns = [
- "class varchar(30)",
- "color varchar(11)",
- "n_cells integer",
- ]
- nbands = len(self.GetGroupLayers(self.g["group"], self.g["subgroup"]))
- for statistic, format in (
- ("min", "integer"),
- ("mean", "double precision"),
- ("max", "integer"),
- ):
- for i in range(nbands):
- # 10 characters limit?
- columns.append(
- "band%(band)d_%(stat)s %(format)s"
- % {"band": i + 1, "stat": statistic, "format": format}
- )
- if 0 != RunCommand(
- "v.db.addtable", map=vectorName, columns=columns, parent=self
- ):
- wx.EndBusyCursor()
- return False
- try:
- dbInfo = grass.vector_db(vectorName)[1]
- except KeyError:
- wx.EndBusyCursor()
- return False
- dbFile = tempfile.NamedTemporaryFile(mode="w", delete=False)
- if dbInfo["driver"] != "dbf":
- dbFile.write("BEGIN\n")
- # populate table
- for cat in self.stats_data.GetCategories():
- stat = self.stats_data.GetStatistics(cat)
- self._runDBUpdate(
- dbFile, table=dbInfo["table"], column="class", value=stat.name, cat=cat
- )
- self._runDBUpdate(
- dbFile, table=dbInfo["table"], column="color", value=stat.color, cat=cat
- )
- if not stat.IsReady():
- continue
- self._runDBUpdate(
- dbFile,
- table=dbInfo["table"],
- column="n_cells",
- value=stat.ncells,
- cat=cat,
- )
- for i in range(nbands):
- self._runDBUpdate(
- dbFile,
- table=dbInfo["table"],
- column="band%d_min" % (i + 1),
- value=stat.bands[i].min,
- cat=cat,
- )
- self._runDBUpdate(
- dbFile,
- table=dbInfo["table"],
- column="band%d_mean" % (i + 1),
- value=stat.bands[i].mean,
- cat=cat,
- )
- self._runDBUpdate(
- dbFile,
- table=dbInfo["table"],
- column="band%d_max" % (i + 1),
- value=stat.bands[i].max,
- cat=cat,
- )
- if dbInfo["driver"] != "dbf":
- dbFile.write("COMMIT\n")
- dbFile.file.close()
- ret = RunCommand(
- "db.execute",
- input=dbFile.name,
- driver=dbInfo["driver"],
- database=dbInfo["database"],
- )
- wx.EndBusyCursor()
- os.remove(dbFile.name)
- if ret != 0:
- return False
- return True
- def _runDBUpdate(self, tmpFile, table, column, value, cat):
- """Helper function for UPDATE statement
- :param tmpFile: file where to write UPDATE statements
- :param table: table name
- :param column: name of updated column
- :param value: new value
- :param cat: which category to update
- """
- if isinstance(value, (int, float)):
- tmpFile.write(
- "UPDATE %s SET %s = %d WHERE cat = %d\n" % (table, column, value, cat)
- )
- else:
- tmpFile.write(
- "UPDATE %s SET %s = '%s' WHERE cat = %d\n" % (table, column, value, cat)
- )
- def OnCategoryManager(self, event):
- """Show category management dialog"""
- if self.dialogs["classManager"] is None:
- dlg = IClassCategoryManagerDialog(self)
- dlg.CenterOnParent()
- dlg.Show()
- self.dialogs["classManager"] = dlg
- else:
- if not self.dialogs["classManager"].IsShown():
- self.dialogs["classManager"].Show()
- def CategoryChanged(self, currentCat):
- """Updates everything which depends on current category.
- Updates number of stddev, histograms, layer in preview display.
- """
- if currentCat:
- stat = self.stats_data.GetStatistics(currentCat)
- nstd = stat.nstd
- self.toolbars["iClass"].UpdateStddev(nstd)
- self.plotPanel.UpdateCategory(currentCat)
- self.plotPanel.OnPlotTypeSelected(None)
- name = stat.rasterName
- name = self.previewMapManager.GetAlias(name)
- if name:
- self.previewMapManager.SelectLayer(name)
- self.categoryChanged.emit(cat=currentCat)
- def DeleteAreas(self, cats):
- """Removes all training areas of given categories
- :param cats: list of categories to be deleted
- """
- self.firstMapWindow.GetDigit().DeleteAreasByCat(cats)
- self.firstMapWindow.UpdateMap(render=False, renderVector=True)
- def HighlightCategory(self, cats):
- """Highlight araes given by category"""
- self.firstMapWindow.GetDigit().GetDisplay().SetSelected(cats, layer=1)
- self.firstMapWindow.UpdateMap(render=False, renderVector=True)
- def ZoomToAreasByCat(self, cat):
- """Zoom to areas given by category"""
- n, s, w, e = self.GetFirstWindow().GetDigit().GetDisplay().GetRegionSelected()
- self.GetFirstMap().GetRegion(n=n, s=s, w=w, e=e, update=True)
- self.GetFirstMap().AdjustRegion()
- self.GetFirstMap().AlignExtentFromDisplay()
- self.GetFirstWindow().UpdateMap(render=True, renderVector=True)
- def UpdateRasterName(self, newName, cat):
- """Update alias of raster map when category name is changed"""
- origName = self.stats_data.GetStatistics(cat).rasterName
- self.previewMapManager.SetAlias(origName, self._addSuffix(newName))
- def StddevChanged(self, cat, nstd):
- """Standard deviation multiplier changed, rerender map, histograms"""
- stat = self.stats_data.GetStatistics(cat)
- stat.SetStatistics({"nstd": nstd})
- if not stat.IsReady():
- return
- raster = stat.rasterName
- cstat = self.cStatisticsDict[cat]
- I_iclass_statistics_set_nstd(cstat, nstd)
- I_iclass_create_raster(cstat, self.refer, raster)
- self.Render(self.GetSecondWindow())
- stat.SetBandStatistics(cstat)
- self.plotPanel.StddevChanged()
- def UpdateChangeState(self, changes):
- """Informs if any important changes happened
- since last analysis computation.
- """
- self.changes = changes
- def AddRasterMap(self, name, firstMap=True, secondMap=True):
- """Add raster map to Map"""
- cmdlist = ["d.rast", "map=%s" % name]
- if firstMap:
- self.GetFirstMap().AddLayer(
- ltype="raster",
- command=cmdlist,
- active=True,
- name=name,
- hidden=False,
- opacity=1.0,
- render=False,
- )
- self.Render(self.GetFirstWindow())
- if secondMap:
- self.GetSecondMap().AddLayer(
- ltype="raster",
- command=cmdlist,
- active=True,
- name=name,
- hidden=False,
- opacity=1.0,
- render=False,
- )
- self.Render(self.GetSecondWindow())
- def AddTrainingAreaMap(self):
- """Add vector map with training areas to Map (training
- sub-display)"""
- vname = self.CreateTempVector()
- if vname:
- self.trainingAreaVector = vname
- else:
- GMessage(parent=self, message=_("Failed to create temporary vector map."))
- return
- # use 'hidden' for temporary maps (TODO: do it better)
- mapLayer = self.GetFirstMap().AddLayer(
- ltype="vector",
- command=["d.vect", "map=%s" % vname],
- name=vname,
- active=False,
- hidden=True,
- )
- self.toolbars["vdigit"].StartEditing(mapLayer)
- self.poMapInfo = self.GetFirstWindow().GetDigit().GetMapInfo()
- self.Render(self.GetFirstWindow())
- def OnRunAnalysis(self, event):
- """Run analysis and update plots"""
- if self.RunAnalysis():
- currentCat = self.GetCurrentCategoryIdx()
- self.plotPanel.UpdatePlots(
- group=self.g["group"],
- subgroup=self.g["subgroup"],
- currentCat=currentCat,
- stats_data=self.stats_data,
- )
- def RunAnalysis(self):
- """Run analysis
- Calls C functions to compute all statistics and creates raster maps.
- Signatures are created but signature file is not.
- """
- if not self.CheckInput(group=self.g["group"], vector=self.trainingAreaVector):
- return
- for statistic in self.cStatisticsDict.values():
- I_iclass_free_statistics(statistic)
- self.cStatisticsDict = {}
- # init Ref struct with the files in group */
- I_free_group_ref(self.refer)
- if not I_iclass_init_group(self.g["group"], self.g["subgroup"], self.refer):
- return False
- I_free_signatures(self.signatures)
- ret = I_iclass_init_signatures(self.signatures, self.refer)
- if not ret:
- GMessage(
- parent=self,
- message=_(
- "There was an error initializing signatures. "
- "Check GUI console for any error messages."
- ),
- )
- I_free_signatures(self.signatures)
- return False
- # why create copy
- # cats = self.statisticsList[:]
- cats = self.stats_data.GetCategories()
- for i in cats:
- stats = self.stats_data.GetStatistics(i)
- statistics_obj = IClass_statistics()
- statistics = pointer(statistics_obj)
- I_iclass_init_statistics(
- statistics, stats.category, stats.name, stats.color, stats.nstd
- )
- ret = I_iclass_analysis(
- statistics,
- self.refer,
- self.poMapInfo,
- "1",
- self.g["group"],
- stats.rasterName,
- )
- if ret > 0:
- # tests
- self.cStatisticsDict[i] = statistics
- stats.SetFromcStatistics(statistics)
- stats.SetReady()
- # stat is already part of stats_data?
- # self.statisticsDict[stats.category] = stats
- self.ConvertToNull(name=stats.rasterName)
- self.previewMapManager.AddLayer(
- name=stats.rasterName,
- alias=self._addSuffix(stats.name),
- resultsLayer=True,
- )
- # write statistics
- I_iclass_add_signature(self.signatures, statistics)
- elif ret == 0:
- GMessage(
- parent=self,
- message=_("No area in category %s. Category skipped.")
- % stats.category,
- )
- I_iclass_free_statistics(statistics)
- else:
- GMessage(parent=self, message=_("Analysis failed."))
- I_iclass_free_statistics(statistics)
- self.UpdateChangeState(changes=False)
- return True
- def _addSuffix(self, name):
- suffix = _("results")
- return "_".join((name, suffix))
- def OnSaveSigFile(self, event):
- """Asks for signature file name and saves it."""
- if not self.g["group"]:
- GMessage(parent=self, message=_("No imagery group selected."))
- return
- if self.changes:
- qdlg = wx.MessageDialog(
- parent=self,
- message=_(
- "Due to recent changes in classes, "
- "signatures can be outdated and should be recalculated. "
- "Do you still want to continue?"
- ),
- caption=_("Outdated signatures"),
- style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION | wx.CENTRE,
- )
- if qdlg.ShowModal() == wx.ID_YES:
- qdlg.Destroy()
- else:
- qdlg.Destroy()
- return
- if not self.signatures.contents.nsigs:
- GMessage(
- parent=self,
- message=_(
- "Signatures are not valid. Recalculate them and then try again."
- ),
- )
- return
- dlg = IClassSignatureFileDialog(self, file=self.sigFile)
- if dlg.ShowModal() == wx.ID_OK:
- if os.path.exists(dlg.GetFileName(fullPath=True)):
- qdlg = wx.MessageDialog(
- parent=self,
- message=_(
- "A signature file named %s already exists.\n"
- "Do you want to replace it?"
- )
- % dlg.GetFileName(),
- caption=_("File already exists"),
- style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION | wx.CENTRE,
- )
- if qdlg.ShowModal() == wx.ID_YES:
- qdlg.Destroy()
- else:
- qdlg.Destroy()
- return
- self.sigFile = dlg.GetFileName()
- self.WriteSignatures(self.signatures, self.sigFile)
- dlg.Destroy()
- def InitStatistics(self):
- """Initialize variables and c structures necessary for
- computing statistics.
- """
- self.g = {"group": None, "subgroup": None}
- self.sigFile = None
- self.stats_data = StatisticsData()
- self.cStatisticsDict = {}
- self.signatures_obj = Signature()
- self.signatures = pointer(self.signatures_obj)
- I_init_signatures(self.signatures, 0) # must be freed on exit
- refer_obj = Ref()
- self.refer = pointer(refer_obj)
- I_init_group_ref(self.refer) # must be freed on exit
- def WriteSignatures(self, signatures, filename):
- """Writes current signatures to signature file
- :param signatures: signature (c structure)
- :param filename: signature file name
- """
- I_iclass_write_signatures(signatures, filename)
- def CheckInput(self, group, vector):
- """Check if input is valid"""
- # check if group is ok
- # TODO check subgroup
- if not group:
- GMessage(
- parent=self,
- message=_("No imagery group selected. " "Operation canceled."),
- )
- return False
- groupLayers = self.GetGroupLayers(self.g["group"], self.g["subgroup"])
- nLayers = len(groupLayers)
- if nLayers <= 1:
- GMessage(
- parent=self,
- message=_(
- "Group <%(group)s> does not have enough files "
- "(it has %(files)d files). Operation canceled."
- )
- % {"group": group, "files": nLayers},
- )
- return False
- # check if vector has any areas
- if self.GetAreasCount() == 0:
- GMessage(parent=self, message=_("No areas given. " "Operation canceled."))
- return False
- # check if vector is inside raster
- regionBox = bound_box()
- Vect_get_map_box(self.poMapInfo, byref(regionBox))
- rasterInfo = grass.raster_info(groupLayers[0])
- if (
- regionBox.N > rasterInfo["north"]
- or regionBox.S < rasterInfo["south"]
- or regionBox.E > rasterInfo["east"]
- or regionBox.W < rasterInfo["west"]
- ):
- GMessage(
- parent=self,
- message=_(
- "Vector features are outside raster layers. " "Operation canceled."
- ),
- )
- return False
- return True
- def GetAreasCount(self):
- """Returns number of not dead areas"""
- count = 0
- numAreas = Vect_get_num_areas(self.poMapInfo)
- for i in range(numAreas):
- if Vect_area_alive(self.poMapInfo, i + 1):
- count += 1
- return count
- def GetGroupLayers(self, group, subgroup=None):
- """Get layers in subgroup (expecting same name for group and subgroup)
- .. todo::
- consider moving this function to core module for convenient
- """
- kwargs = {}
- if subgroup:
- kwargs["subgroup"] = subgroup
- res = RunCommand("i.group", flags="g", group=group, read=True, **kwargs).strip()
- if res.splitlines()[0]:
- return sorted(res.splitlines())
- return []
- def ConvertToNull(self, name):
- """Sets value which represents null values for given raster map.
- :param name: raster map name
- """
- RunCommand("r.null", map=name, setnull=0)
- def GetCurrentCategoryIdx(self):
- """Returns current category number"""
- return self.toolbars["iClass"].GetSelectedCategoryIdx()
- def OnZoomIn(self, event):
- """Enable zooming for plots"""
- super(IClassMapPanel, self).OnZoomIn(event)
- self.plotPanel.EnableZoom(type=1)
- def OnZoomOut(self, event):
- """Enable zooming for plots"""
- super(IClassMapPanel, self).OnZoomOut(event)
- self.plotPanel.EnableZoom(type=-1)
- def OnPan(self, event):
- """Enable panning for plots"""
- super(IClassMapPanel, self).OnPan(event)
- self.plotPanel.EnablePan()
- def OnPointer(self, event):
- """Set pointer mode.
- .. todo::
- pointers need refactoring
- """
- self.GetFirstWindow().SetModePointer()
- self.GetSecondWindow().SetModePointer()
- def GetMapManagers(self):
- """Get map managers of wxIClass
- :return: trainingMapManager, previewMapManager
- """
- return self.trainingMapManager, self.previewMapManager
- class IClassMapDisplay(FrameMixin, IClassMapPanel):
- """Map display for wrapping map panel with frame methods"""
- def __init__(self, parent, giface, **kwargs):
- # init map panel
- IClassMapPanel.__init__(
- self,
- parent=parent,
- giface=giface,
- **kwargs,
- )
- # set system icon
- parent.SetIcon(
- wx.Icon(
- os.path.join(globalvar.ICONDIR, "grass_map.ico"), wx.BITMAP_TYPE_ICO
- )
- )
- # bindings
- parent.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
- # extend shortcuts and create frame accelerator table
- self.shortcuts_table.append((self.OnFullScreen, wx.ACCEL_NORMAL, wx.WXK_F11))
- self._initShortcuts()
- # add Map Display panel to Map Display frame
- sizer = wx.BoxSizer(wx.VERTICAL)
- sizer.Add(self, proportion=1, flag=wx.EXPAND)
- parent.SetSizer(sizer)
- parent.Layout()
- class MapManager:
- """Class for managing map renderer.
- It is connected with iClassMapManagerToolbar.
- """
- def __init__(self, frame, mapWindow, Map):
- """
- It is expected that \a mapWindow is connected with \a Map.
- :param frame: application main window
- :param mapWindow: map window instance
- :param map: map renderer instance
- """
- self.map = Map
- self.frame = frame
- self.mapWindow = mapWindow
- self.toolbar = None
- self.layerName = {}
- def SetToolbar(self, toolbar):
- self.toolbar = toolbar
- def AddLayer(self, name, alias=None, resultsLayer=False):
- """Adds layer to Map and update toolbar
- :param str name: layer (raster) name
- :param str resultsLayer: True if layer is temp. raster showing the results of computation
- """
- if resultsLayer and name in [
- layer.GetName() for layer in self.map.GetListOfLayers(name=name)
- ]:
- self.frame.Render(self.mapWindow)
- return
- cmdlist = ["d.rast", "map=%s" % name]
- self.map.AddLayer(
- ltype="raster",
- command=cmdlist,
- active=True,
- name=name,
- hidden=False,
- opacity=1.0,
- render=True,
- )
- self.frame.Render(self.mapWindow)
- if alias is not None:
- self.layerName[alias] = name
- name = alias
- else:
- self.layerName[name] = name
- self.toolbar.choice.Insert(name, 0)
- self.toolbar.choice.SetSelection(0)
- def AddLayerRGB(self, cmd):
- """Adds RGB layer and update toolbar.
- :param cmd: d.rgb command as a list
- """
- name = []
- for param in cmd:
- if "=" in param:
- name.append(param.split("=")[1])
- name = ",".join(name)
- self.map.AddLayer(
- ltype="rgb",
- command=cmd,
- active=True,
- name=name,
- hidden=False,
- opacity=1.0,
- render=True,
- )
- self.frame.Render(self.mapWindow)
- self.layerName[name] = name
- self.toolbar.choice.Insert(name, 0)
- self.toolbar.choice.SetSelection(0)
- def RemoveTemporaryLayer(self, name):
- """Removes temporary layer (if exists) from Map and and updates toolbar.
- :param name: real name of layer
- """
- # check if layer is loaded
- layers = self.map.GetListOfLayers(ltype="raster")
- idx = None
- for i, layer in enumerate(layers):
- if name == layer.GetName():
- idx = i
- break
- if idx is None:
- return
- # remove it from Map
- self.map.RemoveLayer(name=name)
- # update inner list of layers
- alias = self.GetAlias(name)
- if alias not in self.layerName:
- return
- del self.layerName[alias]
- # update choice
- idx = self.toolbar.choice.FindString(alias)
- if idx != wx.NOT_FOUND:
- self.toolbar.choice.Delete(idx)
- if not self.toolbar.choice.IsEmpty():
- self.toolbar.choice.SetSelection(0)
- self.frame.Render(self.mapWindow)
- def Render(self):
- """
- .. todo::
- giface shoud be used instead of this method"""
- self.frame.Render(self.mapWindow)
- def RemoveLayer(self, name, idx):
- """Removes layer from Map and update toolbar"""
- name = self.layerName[name]
- self.map.RemoveLayer(name=name)
- del self.layerName[name]
- self.toolbar.choice.Delete(idx)
- if not self.toolbar.choice.IsEmpty():
- self.toolbar.choice.SetSelection(0)
- self.frame.Render(self.mapWindow)
- def SelectLayer(self, name):
- """Moves selected layer to top"""
- layers = self.map.GetListOfLayers(ltype="rgb") + self.map.GetListOfLayers(
- ltype="raster"
- )
- idx = None
- for i, layer in enumerate(layers):
- if self.layerName[name] == layer.GetName():
- idx = i
- break
- if idx is not None: # should not happen
- layers.append(layers.pop(idx))
- choice = self.toolbar.choice
- idx = choice.FindString(name)
- choice.Delete(idx)
- choice.Insert(name, 0)
- choice.SetSelection(0)
- # layers.reverse()
- self.map.SetLayers(layers)
- self.frame.Render(self.mapWindow)
- def SetOpacity(self, name):
- """Sets opacity of layers."""
- name = self.layerName[name]
- layers = self.map.GetListOfLayers(name=name)
- if not layers:
- return
- # works for first layer only
- oldOpacity = layers[0].GetOpacity()
- dlg = SetOpacityDialog(self.frame, opacity=oldOpacity)
- dlg.applyOpacity.connect(
- lambda value: self._changeOpacity(layer=layers[0], opacity=value)
- )
- if dlg.ShowModal() == wx.ID_OK:
- self._changeOpacity(layer=layers[0], opacity=dlg.GetOpacity())
- dlg.Destroy()
- def _changeOpacity(self, layer, opacity):
- self.map.ChangeOpacity(layer=layer, opacity=opacity)
- self.frame.Render(self.mapWindow)
- def GetAlias(self, name):
- """Returns alias for layer"""
- name = [k for k, v in six.iteritems(self.layerName) if v == name]
- if name:
- return name[0]
- return None
- def SetAlias(self, original, alias):
- name = self.GetAlias(original)
- if name:
- self.layerName[alias] = original
- del self.layerName[name]
- idx = self.toolbar.choice.FindString(name)
- if idx != wx.NOT_FOUND:
- self.toolbar.choice.SetString(idx, alias)
- def test():
- app = wx.App()
- frame = wx.Frame(
- parent=None,
- size=globalvar.MAP_WINDOW_SIZE,
- title=_("Supervised Classification Tool"),
- )
- frame = IClassMapDisplay(parent=frame)
- frame.Show()
- app.MainLoop()
- if __name__ == "__main__":
- test()
|