georect.py 61 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735
  1. """
  2. @package georect.py
  3. @brief Georectification module for GRASS GIS. Includes ground control
  4. point management and interactive point and click GCP creation
  5. Classes:
  6. - GeorectWizard
  7. - LocationPage
  8. - GroupPage
  9. - DispMapPage
  10. - GCP
  11. - GCPList
  12. - VectGroup
  13. - EditGCP
  14. - GrSettingsDialog
  15. (C) 2006-2008 by the GRASS Development Team
  16. This program is free software under the GNU General Public License
  17. (>=v2). Read the file COPYING that comes with GRASS for details.
  18. @author Michael Barton
  19. @author Updated by Martin Landa <landa.martin gmail.com>
  20. """
  21. import os
  22. import sys
  23. import tempfile
  24. import shutil
  25. import time
  26. import wx
  27. from wx.lib.mixins.listctrl import CheckListCtrlMixin, ListCtrlAutoWidthMixin, TextEditMixin
  28. import wx.lib.colourselect as csel
  29. import wx.wizard as wiz
  30. import grass
  31. import globalvar
  32. import mapdisp
  33. import render
  34. import toolbars
  35. import menuform
  36. import gselect
  37. import gcmd
  38. import utils
  39. from debug import Debug as Debug
  40. from icon import Icons as Icons
  41. from location_wizard import TitledPage as TitledPage
  42. from preferences import globalSettings as UserSettings
  43. try:
  44. import subprocess # Not needed if GRASS commands could actually be quiet
  45. except:
  46. CompatPath = globalvar.ETCWXDIR
  47. sys.path.append(CompatPath)
  48. from compat import subprocess
  49. gmpath = os.path.join(globalvar.ETCWXDIR, "icons")
  50. sys.path.append(gmpath)
  51. #
  52. # global variables
  53. #
  54. global xy_map
  55. global maptype
  56. xy_map = ''
  57. maptype = 'cell'
  58. class GeorectWizard(object):
  59. """
  60. Start wizard here and finish wizard here
  61. """
  62. def __init__(self, parent):
  63. self.parent = parent # GMFrame
  64. #
  65. # get environmental variables
  66. #
  67. self.grassdatabase = grass.gisenv()['GISDBASE']
  68. #
  69. # read original environment settings
  70. #
  71. self.orig_gisrc = os.environ['GISRC']
  72. self.gisrc_dict = {}
  73. try:
  74. f = open(self.orig_gisrc, 'r')
  75. for line in f.readlines():
  76. if line != '':
  77. line = line.replace('\n', '').strip()
  78. key, value = line.split(':')
  79. self.gisrc_dict[key.strip()] = value.strip()
  80. finally:
  81. f.close()
  82. self.currentlocation = self.gisrc_dict['LOCATION_NAME']
  83. self.currentmapset = self.gisrc_dict['MAPSET']
  84. # location for xy map to georectify
  85. self.newlocation = ''
  86. # mapset for xy map to georectify
  87. self.newmapset = ''
  88. # GISRC file for source location/mapset of map(s) to georectify
  89. self.new_gisrc = ''
  90. #
  91. # define wizard pages
  92. #
  93. self.wizard = wiz.Wizard(parent=parent, id=wx.ID_ANY, title=_("Setup for georectification"))
  94. self.startpage = LocationPage(self.wizard, self)
  95. self.grouppage = GroupPage(self.wizard, self)
  96. self.mappage = DispMapPage(self.wizard, self)
  97. #
  98. # set the initial order of the pages
  99. #
  100. self.startpage.SetNext(self.grouppage)
  101. self.grouppage.SetPrev(self.startpage)
  102. self.grouppage.SetNext(self.mappage)
  103. self.mappage.SetPrev(self.grouppage)
  104. #
  105. # do pages layout
  106. #
  107. self.startpage.DoLayout()
  108. self.grouppage.DoLayout()
  109. self.mappage.DoLayout()
  110. self.wizard.FitToPage(self.startpage)
  111. # self.Bind(wx.EVT_CLOSE, self.Cleanup)
  112. # self.parent.Bind(wx.EVT_ACTIVATE, self.OnGLMFocus)
  113. success = False
  114. #
  115. # run wizard
  116. #
  117. if self.wizard.RunWizard(self.startpage):
  118. success = self.OnWizFinished()
  119. if success == False:
  120. wx.MessageBox(parent=self.parent,
  121. message=_("Georectifying setup canceled."),
  122. caption=_("Georectify"),
  123. style=wx.OK | wx.ICON_INFORMATION | wx.CENTRE)
  124. self.Cleanup()
  125. else:
  126. wx.MessageBox(parent=self.parent,
  127. message=_("Georectifying setup canceled."),
  128. caption=_("Georectify"),
  129. style=wx.OK | wx.ICON_INFORMATION | wx.CENTRE)
  130. self.Cleanup()
  131. #
  132. # start display showing xymap
  133. #
  134. if success != False:
  135. # instance of render.Map to be associated with display
  136. self.Map = render.Map(gisrc=self.new_gisrc)
  137. global maptype
  138. global xy_map
  139. #
  140. # add layer to map
  141. #
  142. if maptype == 'cell':
  143. rendertype = 'raster'
  144. cmdlist = ['d.rast', 'map=%s' % xy_map]
  145. else: # -> vector layer
  146. rendertype = 'vector'
  147. cmdlist = ['d.vect', 'map=%s' % xy_map]
  148. self.Map.AddLayer(type=rendertype, command=cmdlist, l_active=True,
  149. name=utils.GetLayerNameFromCmd(cmdlist),
  150. l_hidden=False, l_opacity=1.0, l_render=False)
  151. #
  152. # start GCP form
  153. #
  154. self.gcpmgr = GCP(self.parent, grwiz=self)
  155. self.gcpmgr.Show()
  156. #
  157. # start map display
  158. #
  159. self.xy_mapdisp = mapdisp.MapFrame(self.gcpmgr, title=_("Set ground control points (GCPs)"),
  160. size=globalvar.MAP_WINDOW_SIZE,
  161. toolbars=["georect"],
  162. Map=self.Map, gismgr=self.parent)
  163. self.gcpmgr.SetMapDisplay(self.xy_mapdisp)
  164. self.mapwin = self.xy_mapdisp.MapWindow
  165. # set mouse characteristics
  166. self.mapwin.mouse['box'] = 'point'
  167. self.mapwin.mouse["use"] == "pointer"
  168. self.mapwin.zoomtype = 0
  169. self.mapwin.pen = wx.Pen(colour='black', width=2, style=wx.SOLID)
  170. self.mapwin.SetCursor(self.xy_mapdisp.cursors["cross"])
  171. #
  172. # show new display & draw map
  173. #
  174. self.xy_mapdisp.Show()
  175. else:
  176. self.Cleanup()
  177. def SetSrcEnv(self, location, mapset):
  178. """Create environment to use for location and mapset
  179. that are the source of the file(s) to georectify
  180. @param location source location
  181. @param mapset source mapset
  182. @return False on error
  183. @return True on success
  184. """
  185. self.newlocation = location
  186. self.newmapset = mapset
  187. # check to see if we are georectifying map in current working location/mapset
  188. if self.newlocation == self.currentlocation and self.newmapset == self.currentmapset:
  189. return False
  190. self.gisrc_dict['LOCATION_NAME'] = location
  191. self.gisrc_dict['MAPSET'] = mapset
  192. self.new_gisrc = utils.GetTempfile()
  193. try:
  194. f = open(self.new_gisrc, mode='w')
  195. for line in self.gisrc_dict.items():
  196. f.write(line[0] + ": " + line[1] + "\n")
  197. finally:
  198. f.close()
  199. return True
  200. def SwitchEnv(self, grc):
  201. """
  202. Switches between original working location/mapset and
  203. location/mapset that is source of file(s) to georectify
  204. """
  205. # check to see if we are georectifying map in current working location/mapset
  206. if self.newlocation == self.currentlocation and self.newmapset == self.currentmapset:
  207. return False
  208. if grc == 'original':
  209. os.environ["GISRC"] = str(self.orig_gisrc)
  210. elif grc == 'new':
  211. os.environ["GISRC"] = str(self.new_gisrc)
  212. return True
  213. def OnWizFinished(self):
  214. # self.Cleanup()
  215. return True
  216. def OnGLMFocus(self, event):
  217. """Layer Manager focus"""
  218. # self.SwitchEnv('original')
  219. event.Skip()
  220. def Cleanup(self):
  221. """Return to current location and mapset"""
  222. self.SwitchEnv('original')
  223. self.parent.georectifying = None
  224. if hasattr(self, "xy_mapdisp"):
  225. self.xy_mapdisp.Close()
  226. self.xy_mapdisp = None
  227. self.wizard.Destroy()
  228. class LocationPage(TitledPage):
  229. """
  230. Set map type (raster or vector) to georectify and
  231. select location/mapset of map(s) to georectify.
  232. """
  233. def __init__(self, wizard, parent):
  234. TitledPage.__init__(self, wizard, _("Select map type and location/mapset"))
  235. self.parent = parent
  236. self.grassdatabase = self.parent.grassdatabase
  237. self.xylocation = ''
  238. self.xymapset = ''
  239. tmplist = os.listdir(self.grassdatabase)
  240. self.locList = []
  241. self.mapsetList = []
  242. #
  243. # create a list of valid locations
  244. #
  245. for item in tmplist:
  246. if os.path.isdir(os.path.join(self.grassdatabase, item)) and \
  247. os.path.exists(os.path.join(self.grassdatabase, item, 'PERMANENT')):
  248. self.locList.append(item)
  249. utils.ListSortLower(self.locList)
  250. #
  251. # layout
  252. #
  253. self.sizer.AddGrowableCol(2)
  254. # map type
  255. self.rb_maptype = wx.RadioBox(parent=self, id=wx.ID_ANY,
  256. label=' %s ' % _("Map type to georectify"),
  257. choices=[_('raster'), _('vector')],
  258. majorDimension=wx.RA_SPECIFY_COLS)
  259. self.sizer.Add(item=self.rb_maptype,
  260. flag=wx.ALIGN_CENTER | wx.ALL | wx.EXPAND, border=5,
  261. pos=(1, 1), span=(1, 2))
  262. # location
  263. self.sizer.Add(item=wx.StaticText(parent=self, id=wx.ID_ANY, label=_('Select source location:')),
  264. flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5,
  265. pos=(2, 1))
  266. self.cb_location = wx.ComboBox(parent=self, id=wx.ID_ANY,
  267. choices = self.locList, size=(300, -1),
  268. style=wx.CB_DROPDOWN | wx.CB_READONLY)
  269. self.sizer.Add(item=self.cb_location,
  270. flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5,
  271. pos=(2, 2))
  272. # mapset
  273. self.sizer.Add(item=wx.StaticText(parent=self, id=wx.ID_ANY, label=_('Select source mapset:')),
  274. flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5,
  275. pos=(3, 1))
  276. self.cb_mapset = wx.ComboBox(parent=self, id=wx.ID_ANY,
  277. choices = self.mapsetList, size=(300, -1),
  278. style=wx.CB_DROPDOWN | wx.CB_READONLY)
  279. self.sizer.Add(item=self.cb_mapset,
  280. flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5,
  281. pos=(3,2))
  282. #
  283. # bindings
  284. #
  285. self.Bind(wx.EVT_RADIOBOX, self.OnMaptype, self.rb_maptype)
  286. self.Bind(wx.EVT_COMBOBOX, self.OnLocation, self.cb_location)
  287. self.Bind(wx.EVT_COMBOBOX, self.OnMapset, self.cb_mapset)
  288. self.Bind(wiz.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
  289. self.Bind(wiz.EVT_WIZARD_PAGE_CHANGED, self.OnEnterPage)
  290. # self.Bind(wx.EVT_CLOSE, self.parent.Cleanup)
  291. def OnMaptype(self,event):
  292. """Change map type"""
  293. global maptype
  294. if event.GetInt() == 0:
  295. maptype = 'cell'
  296. else:
  297. maptype = 'vector'
  298. def OnLocation(self, event):
  299. """Sets source location for map(s) to georectify"""
  300. self.xylocation = event.GetString()
  301. #create a list of valid mapsets
  302. tmplist = os.listdir(os.path.join(self.grassdatabase, self.xylocation))
  303. self.mapsetList = []
  304. for item in tmplist:
  305. if os.path.isdir(os.path.join(self.grassdatabase, self.xylocation, item)) and \
  306. os.path.exists(os.path.join(self.grassdatabase, self.xylocation, item, 'WIND')):
  307. if item != 'PERMANENT':
  308. self.mapsetList.append(item)
  309. self.xymapset = 'PERMANENT'
  310. utils.ListSortLower(self.mapsetList)
  311. self.mapsetList.insert(0, 'PERMANENT')
  312. self.cb_mapset.SetItems(self.mapsetList)
  313. self.cb_mapset.SetStringSelection(self.xymapset)
  314. if not wx.FindWindowById(wx.ID_FORWARD).IsEnabled():
  315. wx.FindWindowById(wx.ID_FORWARD).Enable(True)
  316. def OnMapset(self, event):
  317. """Sets source mapset for map(s) to georectify"""
  318. if self.xylocation == '':
  319. wx.MessageBox(_('You must select a valid location before selecting a mapset'))
  320. return
  321. self.xymapset = event.GetString()
  322. if not wx.FindWindowById(wx.ID_FORWARD).IsEnabled():
  323. wx.FindWindowById(wx.ID_FORWARD).Enable(True)
  324. def OnPageChanging(self, event=None):
  325. if event.GetDirection() and \
  326. (self.xylocation == '' or self.xymapset == ''):
  327. wx.MessageBox(_('You must select a valid location and mapset in order to continue'))
  328. event.Veto()
  329. return
  330. self.parent.SetSrcEnv(self.xylocation, self.xymapset)
  331. def OnEnterPage(self, event=None):
  332. if self.xylocation == '' or self.xymapset == '':
  333. wx.FindWindowById(wx.ID_FORWARD).Enable(False)
  334. else:
  335. wx.FindWindowById(wx.ID_FORWARD).Enable(True)
  336. class GroupPage(TitledPage):
  337. """
  338. Set group to georectify. Create group if desired.
  339. """
  340. def __init__(self, wizard, parent):
  341. TitledPage.__init__(self, wizard, _("Select image/map group to georectify"))
  342. self.parent = parent
  343. self.grassdatabase = self.parent.grassdatabase
  344. self.groupList = []
  345. self.xylocation = ''
  346. self.xymapset = ''
  347. self.xygroup = ''
  348. # default extension
  349. self.extension = 'georect' + str(os.getpid())
  350. #
  351. # layout
  352. #
  353. self.sizer.AddGrowableCol(2)
  354. # group
  355. self.sizer.Add(item=wx.StaticText(parent=self, id=wx.ID_ANY, label=_('Select group:')),
  356. flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5,
  357. pos=(1, 1))
  358. self.cb_group = wx.ComboBox(parent=self, id=wx.ID_ANY,
  359. choices=self.groupList, size=(350, -1),
  360. style=wx.CB_DROPDOWN | wx.CB_READONLY)
  361. self.sizer.Add(item=self.cb_group,
  362. flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5,
  363. pos=(1, 2))
  364. # create group
  365. self.sizer.Add(item=wx.StaticText(parent=self, id=wx.ID_ANY, label=_('Create group if none exists')),
  366. flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5,
  367. pos=(2, 1))
  368. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  369. self.btn_mkgroup = wx.Button(parent=self, id=wx.ID_ANY, label=_("Create/edit group..."))
  370. self.btn_vgroup = wx.Button(parent=self, id=wx.ID_ANY, label=_("Add vector map to group..."))
  371. self.btn_vgroup.Hide()
  372. btnSizer.Add(item=self.btn_mkgroup,
  373. flag=wx.RIGHT, border=5)
  374. btnSizer.Add(item=self.btn_vgroup,
  375. flag=wx.LEFT, border=5)
  376. self.sizer.Add(item=btnSizer,
  377. flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5,
  378. pos=(2, 2))
  379. # extension
  380. self.sizer.Add(item=wx.StaticText(parent=self, id=wx.ID_ANY, label=_('Extension for output maps:')),
  381. flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5,
  382. pos=(3, 1))
  383. self.ext_txt = wx.TextCtrl(parent=self, id=wx.ID_ANY, value="", size=(350,-1))
  384. self.ext_txt.SetValue(self.extension)
  385. self.sizer.Add(item=self.ext_txt,
  386. flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5,
  387. pos=(3, 2))
  388. #
  389. # bindings
  390. #
  391. self.Bind(wx.EVT_COMBOBOX, self.OnGroup, self.cb_group)
  392. self.Bind(wx.EVT_TEXT, self.OnExtension, self.ext_txt)
  393. self.Bind(wiz.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
  394. self.Bind(wiz.EVT_WIZARD_PAGE_CHANGED, self.OnEnterPage)
  395. self.Bind(wx.EVT_CLOSE, self.parent.Cleanup)
  396. def OnGroup(self, event):
  397. self.xygroup = event.GetString()
  398. def OnMkGroup(self, event):
  399. """Create new group in source location/mapset"""
  400. menuform.GUI().ParseCommand(['i.group'],
  401. completed=(self.GetOptData, None, ''),
  402. parentframe=self.parent.parent, modal=True)
  403. def OnVGroup(self, event):
  404. """Add vector maps to group"""
  405. dlg = VectGroup(parent = self,
  406. id = wx.ID_ANY,
  407. grassdb = self.grassdatabase,
  408. location = self.xylocation,
  409. mapset = self.xymapset,
  410. group = self.xygroup)
  411. if dlg.ShowModal() != wx.ID_OK:
  412. return
  413. dlg.MakeVGroup()
  414. self.OnEnterPage()
  415. def GetOptData(self, dcmd, layer, params, propwin):
  416. """Process i.group"""
  417. # update the page
  418. if dcmd:
  419. gcmd.RunCommand(utils.CmdToTuple(dcmd))
  420. self.OnEnterPage()
  421. self.Update()
  422. def OnExtension(self, event):
  423. self.extension = event.GetString()
  424. def OnPageChanging(self, event=None):
  425. if event.GetDirection() and self.xygroup == '':
  426. wx.MessageBox(_('You must select a valid image/map group in order to continue'))
  427. event.Veto()
  428. return
  429. if event.GetDirection() and self.extension == '':
  430. wx.MessageBox(_('You must enter an map name extension in order to continue'))
  431. event.Veto()
  432. return
  433. def OnEnterPage(self, event=None):
  434. global maptype
  435. self.groupList = []
  436. self.xylocation = self.parent.gisrc_dict['LOCATION_NAME']
  437. self.xymapset = self.parent.gisrc_dict['MAPSET']
  438. # create a list of groups in selected mapset
  439. if os.path.isdir(os.path.join(self.grassdatabase,
  440. self.xylocation,
  441. self.xymapset,
  442. 'group')):
  443. tmplist = os.listdir(os.path.join(self.grassdatabase,
  444. self.xylocation,
  445. self.xymapset,
  446. 'group'))
  447. for item in tmplist:
  448. if os.path.isdir(os.path.join(self.grassdatabase,
  449. self.xylocation,
  450. self.xymapset,
  451. 'group',
  452. item)):
  453. self.groupList.append(item)
  454. if maptype == 'cell':
  455. self.btn_vgroup.Hide()
  456. self.Bind(wx.EVT_BUTTON, self.OnMkGroup, self.btn_mkgroup)
  457. elif maptype == 'vector':
  458. self.btn_vgroup.Show()
  459. self.Bind(wx.EVT_BUTTON, self.OnMkGroup, self.btn_mkgroup)
  460. self.Bind(wx.EVT_BUTTON, self.OnVGroup, self.btn_vgroup)
  461. utils.ListSortLower(self.groupList)
  462. self.cb_group.SetItems(self.groupList)
  463. if len(self.groupList) > 0 and \
  464. self.xygroup == '':
  465. self.cb_group.SetSelection(0)
  466. self.xygroup = self.groupList[0]
  467. if self.xygroup == '' or \
  468. self.extension == '':
  469. wx.FindWindowById(wx.ID_FORWARD).Enable(False)
  470. else:
  471. wx.FindWindowById(wx.ID_FORWARD).Enable(True)
  472. # switch to source
  473. self.parent.SwitchEnv('new')
  474. class DispMapPage(TitledPage):
  475. """
  476. Select ungeoreferenced map to display for interactively
  477. setting ground control points (GCPs).
  478. """
  479. def __init__(self, wizard, parent):
  480. TitledPage.__init__(self, wizard,
  481. _("Select image/map to display for ground control point (GCP) creation"))
  482. self.parent = parent
  483. global maptype
  484. #
  485. # layout
  486. #
  487. self.sizer.Add(item=wx.StaticText(parent=self, id=wx.ID_ANY, label=_('Select display image/map:')),
  488. flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5,
  489. pos=(1, 1))
  490. self.selection = gselect.Select(self, id=wx.ID_ANY,
  491. size=globalvar.DIALOG_GSELECT_SIZE)
  492. self.sizer.Add(item=self.selection,
  493. flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5,
  494. pos=(1, 2))
  495. #
  496. # bindings
  497. #
  498. self.selection.Bind(wx.EVT_TEXT, self.OnSelection)
  499. self.Bind(wiz.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
  500. self.Bind(wiz.EVT_WIZARD_PAGE_CHANGED, self.OnEnterPage)
  501. self.Bind(wx.EVT_CLOSE, self.parent.Cleanup)
  502. def OnSelection(self,event):
  503. """Map to display selected"""
  504. global xy_map
  505. xy_map = event.GetString()
  506. if xy_map == '':
  507. wx.FindWindowById(wx.ID_FORWARD).Enable(False)
  508. else:
  509. wx.FindWindowById(wx.ID_FORWARD).Enable(True)
  510. def OnPageChanging(self, event=None):
  511. global xy_map
  512. if event.GetDirection() and xy_map == '':
  513. wx.MessageBox(_('You must select a valid image/map in order to continue'))
  514. event.Veto()
  515. return
  516. self.parent.SwitchEnv('original')
  517. def OnEnterPage(self, event=None):
  518. global maptype
  519. global xy_map
  520. self.selection.SetElementList(maptype,
  521. mapsets = [self.parent.newmapset, ])
  522. if xy_map == '':
  523. wx.FindWindowById(wx.ID_FORWARD).Enable(False)
  524. else:
  525. wx.FindWindowById(wx.ID_FORWARD).Enable(True)
  526. class GCP(wx.Frame):
  527. """
  528. Manages ground control points for georectifying. Calculates RMS statics.
  529. Calls i.rectify or v.transform to georectify map.
  530. """
  531. def __init__(self, parent, grwiz, mapdisp=None, id=wx.ID_ANY,
  532. title=_("Create & manage ground control points"),
  533. size=wx.DefaultSize):
  534. wx.Frame.__init__(self, parent, id, title, size=(625, 300))
  535. self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass_map.ico'), wx.BITMAP_TYPE_ICO))
  536. #
  537. # init variables
  538. #
  539. self.parent = parent # GMFrame
  540. self.parent.georectifying = self
  541. self.mapdisp = mapdisp # XY-location Map Display
  542. self.grwiz = grwiz # GR Wizard
  543. self.grassdatabase = self.grwiz.grassdatabase
  544. self.currentlocation = self.grwiz.currentlocation
  545. self.currentmapset = self.grwiz.currentmapset
  546. self.newlocation = self.grwiz.newlocation
  547. self.newmapset = self.grwiz.newmapset
  548. self.xylocation = self.grwiz.gisrc_dict['LOCATION_NAME']
  549. self.xymapset = self.grwiz.gisrc_dict['MAPSET']
  550. self.xygroup = self.grwiz.grouppage.xygroup
  551. self.extension = self.grwiz.grouppage.extension
  552. self.file = {
  553. 'points' : os.path.join(self.grassdatabase,
  554. self.xylocation,
  555. self.xymapset,
  556. 'group',
  557. self.xygroup,
  558. 'POINTS'),
  559. 'rgrp' : os.path.join(self.grassdatabase,
  560. self.xylocation,
  561. self.xymapset,
  562. 'group',
  563. self.xygroup,
  564. 'REF'),
  565. 'vgrp' : os.path.join(self.grassdatabase,
  566. self.xylocation,
  567. self.xymapset,
  568. 'group',
  569. self.xygroup,
  570. 'VREF'),
  571. 'target' : os.path.join(self.grassdatabase,
  572. self.xylocation,
  573. self.xymapset,
  574. 'group',
  575. self.xygroup,
  576. 'TARGET'),
  577. }
  578. # polynomial order transformation for georectification
  579. self.gr_order = 1
  580. # number of GCPs selected to be used for georectification (checked)
  581. self.GCPcount = 0
  582. # forward RMS error
  583. self.fwd_rmserror = 0.0
  584. # backward RMS error
  585. self.bkw_rmserror = 0.0
  586. # list map coords and ID of map display they came from
  587. self.mapcoordlist = []
  588. self.SetTarget(self.xygroup, self.currentlocation, self.currentmapset)
  589. #
  590. # toolbar and display for xy map
  591. #
  592. self.toolbar = toolbars.GCPToolbar(parent=self, tbframe=self).GetToolbar()
  593. self.SetToolBar(self.toolbar)
  594. self.SetMapDisplay(self.mapdisp)
  595. #
  596. # statusbar
  597. #
  598. self.CreateStatusBar(number=1)
  599. # can put guage into custom statusbar for progress if can figure out how to get progress text from i.rectify
  600. # self.gr_gauge = wx.Gauge(self, -1, 100, (-1,-1), (100, 25))
  601. # self.gr_guage.Pulse()
  602. panel = wx.Panel(parent=self)
  603. #
  604. # do layout
  605. #
  606. sizer = wx.BoxSizer(wx.VERTICAL)
  607. self.rb_grmethod = wx.RadioBox(parent=panel, id=wx.ID_ANY,
  608. label=" %s " % _("Select rectification method for rasters"),
  609. choices=[_('1st order'), _('2nd order'), _('3rd order')],
  610. majorDimension=wx.RA_SPECIFY_COLS)
  611. sizer.Add(item=self.rb_grmethod, proportion=0,
  612. flag=wx.EXPAND | wx.ALL, border=5)
  613. self.clip_to_region = wx.CheckBox(parent=panel, id=wx.ID_ANY,
  614. label=_("clip to computational region in target location"))
  615. sizer.Add(item=self.clip_to_region, proportion=0,
  616. flag=wx.EXPAND | wx.ALL, border=5)
  617. box = wx.StaticBox (parent=panel, id=wx.ID_ANY,
  618. label=" %s " % _("Ground Control Points"))
  619. boxSizer = wx.StaticBoxSizer(box, wx.VERTICAL)
  620. # initialize list control for GCP management
  621. self.list = GCPList(parent=panel, gcp=self)
  622. boxSizer.Add(item=self.list, proportion=1,
  623. flag=wx.EXPAND | wx.ALL, border=3)
  624. sizer.Add(item=boxSizer, proportion=1,
  625. flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border=5)
  626. #
  627. # bindigs
  628. #
  629. self.Bind(wx.EVT_RADIOBOX, self.OnGRMethod, self.rb_grmethod)
  630. self.Bind(wx.EVT_ACTIVATE, self.OnFocus)
  631. self.Bind(wx.EVT_CLOSE, self.OnQuit)
  632. panel.SetSizer(sizer)
  633. # sizer.Fit(self)
  634. def __del__(self):
  635. """Disable georectification mode"""
  636. self.parent.georectifying = None
  637. def SetMapDisplay(self, win):
  638. self.mapdisp = win
  639. if self.mapdisp:
  640. self.list.LoadData()
  641. def SetTarget(self, tgroup, tlocation, tmapset):
  642. """
  643. Sets rectification target to current location and mapset
  644. """
  645. # check to see if we are georectifying map in current working location/mapset
  646. if self.newlocation == self.currentlocation and self.newmapset == self.currentmapset:
  647. gcmd.RunCommand('i.target',
  648. parent = self,
  649. flags = 'c',
  650. group = tgroup)
  651. else:
  652. self.grwiz.SwitchEnv('new')
  653. gcmd.RunCommand('i.target',
  654. parent = self,
  655. group = tgroup,
  656. location = tlocation,
  657. mapset = tmapset)
  658. self.grwiz.SwitchEnv('original')
  659. def AddGCP(self, event):
  660. """
  661. Appends an item to GCP list
  662. """
  663. self.list.AddGCPItem()
  664. # x, y, MapWindow instance
  665. self.mapcoordlist.append({ 'gcpcoord' : (0.0, 0.0, None),
  666. 'mapcoord' : (0.0, 0.0, None) })
  667. def DeleteGCP(self, event):
  668. """
  669. Deletes selected item in GCP list
  670. """
  671. minNumOfItems = self.OnGRMethod(None)
  672. if self.list.GetItemCount() <= minNumOfItems:
  673. wx.MessageBox(parent=self, message=_("At least %d GCPs required. Operation cancelled.") % minNumOfItems,
  674. caption=_("Delete GCP"), style=wx.OK | wx.ICON_INFORMATION)
  675. return
  676. item = self.list.DeleteGCPItem()
  677. del self.mapcoordlist[item]
  678. def ClearGCP(self, event):
  679. """
  680. Clears all values in selected item of GCP list and unchecks it
  681. """
  682. index = self.list.GetSelected()
  683. for i in range(4):
  684. self.list.SetStringItem(index, i, '0.0')
  685. self.list.SetStringItem(index, 4, '')
  686. self.list.SetStringItem(index, 5, '')
  687. self.list.CheckItem(index, False)
  688. self.mapcoordlist[index] = { 'gcpcoord' : (0.0, 0.0, None),
  689. 'mapcoord' : (0.0, 0.0, None) }
  690. def DrawGCP(self, coordtype):
  691. """
  692. Updates GCP and map coord maps and redraws
  693. active (checked) GCP markers
  694. """
  695. col = UserSettings.Get(group='georect', key='symbol', subkey='color')
  696. wxCol = wx.Colour(col[0], col[1], col[2], 255)
  697. wpx = UserSettings.Get(group='georect', key='symbol', subkey='width')
  698. font = self.GetFont()
  699. idx = 0
  700. for gcp in self.mapcoordlist:
  701. mapWin = gcp[coordtype][2]
  702. if not self.list.IsChecked(idx) or not mapWin:
  703. idx += 1
  704. continue
  705. mapWin.pen = wx.Pen(colour=wxCol, width=wpx, style=wx.SOLID)
  706. mapWin.polypen = wx.Pen(colour=wxCol, width=wpx, style=wx.SOLID) # ?
  707. coord = mapWin.Cell2Pixel((gcp[coordtype][0], gcp[coordtype][1]))
  708. mapWin.DrawCross(pdc=mapWin.pdcTmp, coords=coord,
  709. size=5, text={ 'text' : '%s' % str(idx + 1),
  710. 'active' : True,
  711. 'font' : font,
  712. 'color': wxCol,
  713. 'coords': [coord[0] + 5,
  714. coord[1] + 5,
  715. 5,
  716. 5]})
  717. idx += 1
  718. def SetGCPData(self, coordtype, coord, mapdisp=None, check=True):
  719. """
  720. Inserts coordinates from mouse click on map
  721. into selected item of GCP list and checks it for use
  722. """
  723. index = self.list.GetSelected()
  724. if index == wx.NOT_FOUND:
  725. return
  726. coord0 = str(coord[0])
  727. coord1 = str(coord[1])
  728. if coordtype == 'gcpcoord':
  729. self.list.SetStringItem(index, 0, coord0)
  730. self.list.SetStringItem(index, 1, coord1)
  731. self.mapcoordlist[index]['gcpcoord'] = (coord[0], coord[1], mapdisp)
  732. elif coordtype == 'mapcoord':
  733. self.list.SetStringItem(index, 2, coord0)
  734. self.list.SetStringItem(index, 3, coord1)
  735. self.mapcoordlist[index][coordtype] = (coord[0], coord[1], mapdisp)
  736. self.list.CheckItem(index, check)
  737. # self.list.ResizeColumns()
  738. def SaveGCPs(self, event):
  739. """
  740. Make a POINTS file or save GCP coordinates to existing POINTS file
  741. """
  742. self.GCPcount = 0
  743. try:
  744. f = open(self.file['points'], mode='w')
  745. # use os.linesep or '\n' here ???
  746. f.write('# Ground Control Points File\n')
  747. f.write("# \n")
  748. f.write("# target location: " + self.currentlocation + '\n')
  749. f.write("# target mapset: " + self.currentmapset + '\n')
  750. f.write("#unrectified xy georectified east north 1=use gcp point\n")
  751. f.write("#-------------- ----------------------- ---------------\n")
  752. for index in range(self.list.GetItemCount()):
  753. if self.list.IsChecked(index) == True:
  754. check = "1"
  755. self.GCPcount += 1
  756. else:
  757. check = "0"
  758. coord0 = self.list.GetItem(index, 0).GetText()
  759. coord1 = self.list.GetItem(index, 1).GetText()
  760. coord2 = self.list.GetItem(index, 2).GetText()
  761. coord3 = self.list.GetItem(index, 3).GetText()
  762. f.write(coord0 + ' ' + coord1 + ' ' + coord2 + ' ' + coord3 + ' ' + check + '\n')
  763. self.parent.goutput.WriteLog(_('POINTS file <%s> saved') % self.file['points'])
  764. self.SetStatusText(_('POINTS file saved'))
  765. except IOError, err:
  766. wx.MessageBox(parent=self,
  767. message="%s <%s>. %s%s" % (_("Writing POINTS file failed"),
  768. self.file['points'], os.linesep, err),
  769. caption=_("Error"), style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
  770. return
  771. f.close()
  772. def ReadGCPs(self):
  773. """
  774. Reads GCPs and georectified coordinates from POINTS file
  775. """
  776. self.GCPcount = 0
  777. sourceMapWin = self.mapdisp.MapWindow
  778. targetMapWin = self.parent.curr_page.maptree.mapdisplay.MapWindow
  779. try:
  780. f = open(self.file['points'], 'r')
  781. GCPcnt = 0
  782. for line in f.readlines():
  783. if line[0] == '#' or line =='':
  784. continue
  785. line = line.replace('\n', '').strip()
  786. coords = map(float, line.split())
  787. if coords[4] == 1:
  788. check = True
  789. self.GCPcount +=1
  790. else:
  791. check = False
  792. index = self.AddGCP(event=None)
  793. self.SetGCPData('gcpcoord', (coords[0], coords[1]), sourceMapWin, check)
  794. self.SetGCPData('mapcoord', (coords[2], coords[3]), targetMapWin, check)
  795. except IOError, err:
  796. wx.MessageBox(parent=self,
  797. message="%s <%s>. %s%s" % (_("Reading POINTS file failed"),
  798. self.file['points'], os.linesep, err),
  799. caption=_("Error"), style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
  800. return
  801. f.close()
  802. #
  803. # draw GCPs (source and target)
  804. #
  805. sourceMapWin.UpdateMap(render=False, renderVector=False)
  806. if targetMapWin:
  807. targetMapWin.UpdateMap(render=False, renderVector=False)
  808. #
  809. # calculate RMS
  810. #
  811. # FIXME auto calculation on load is not working
  812. #if self.CheckGCPcount():
  813. # self.RMSError(self.xygroup, self.gr_order)
  814. def ReloadGCPs(self, event):
  815. """Reload data from file"""
  816. self.list.LoadData()
  817. def OnFocus(self, event):
  818. # self.grwiz.SwitchEnv('new')
  819. pass
  820. def OnRMS(self, event):
  821. """
  822. RMS button handler
  823. """
  824. self.RMSError(self.xygroup,self.gr_order)
  825. def CheckGCPcount(self, msg=False):
  826. """
  827. Checks to make sure that the minimum number of GCPs have been defined and
  828. are active for the selected transformation order
  829. """
  830. if (self.GCPcount < 3 and self.gr_order == 1) or \
  831. (self.GCPcount < 6 and self.gr_order == 2) or \
  832. (self.GCPcount < 10 and self.gr_order == 3):
  833. if msg:
  834. wx.MessageBox(parent=self,
  835. caption=_("RMS Error"),
  836. message=_('Insufficient points defined and active (checked) '
  837. 'for selected rectification method.\n'
  838. '3+ points needed for 1st order,\n'
  839. '6+ points for 2nd order, and\n'
  840. '10+ points for 3rd order.'),
  841. style=wx.ICON_INFORMATION | wx.ID_OK | wx.CENTRE)
  842. return False
  843. else:
  844. return True
  845. def OnGeorect(self, event):
  846. """
  847. Georectifies map(s) in group using i.rectify or v.transform
  848. """
  849. global maptype
  850. self.SaveGCPs(None)
  851. if self.CheckGCPcount(msg=True) == False:
  852. return
  853. if maptype == 'cell':
  854. self.grwiz.SwitchEnv('new')
  855. cmdlist = ['i.rectify','-a','group=%s' % self.xygroup,
  856. 'extension=%s' % self.extension,'order=%s' % self.gr_order]
  857. if self.clip_to_region:
  858. cmdlist.append('-c')
  859. self.parent.goutput.RunCmd(cmdlist, compReg=False,
  860. switchPage=True)
  861. time.sleep(.1)
  862. self.grwiz.SwitchEnv('original')
  863. elif maptype == 'vector':
  864. # loop through all vectors in VREF
  865. # and move resulting vector to target location
  866. f = open(self.file['vgrp'])
  867. vectlist = []
  868. try:
  869. for vect in f.readlines():
  870. vect = vect.strip('\n')
  871. if len(vect) < 1:
  872. continue
  873. vectlist.append(vect)
  874. finally:
  875. f.close()
  876. for vect in vectlist:
  877. self.grwiz.SwitchEnv('new')
  878. self.outname = vect + '_' + self.extension
  879. self.parent.goutput.WriteLog(text = _('Transforming <%s>...') % vect,
  880. switchPage = True)
  881. xyLayer = []
  882. for layer in grass.vector_db(map = vect).itervalues():
  883. xyLayer.append((layer['driver'],
  884. layer['database'],
  885. layer['table']))
  886. self.parent.goutput.RunCmd(['v.transform',
  887. '--o',
  888. 'input=%s' % vect,
  889. 'output=%s' % self.outname,
  890. 'pointsfile=%s' % self.file['points']],
  891. switchPage = True,
  892. onDone = self.OnGeorectDone)
  893. dbConnect = grass.db_connection()
  894. for layer in xyLayer:
  895. self.parent.goutput.RunCmd(['db.copy',
  896. '--q',
  897. '--o',
  898. 'from_driver=%s' % layer[0],
  899. 'from_database=%s' % layer[1],
  900. 'from_table=%s' % layer[2],
  901. 'to_driver=%s' % dbConnect['driver'],
  902. 'to_database=%s' % dbConnect['database'],
  903. 'to_table=%s' % layer[2] + '_' + self.extension])
  904. def OnGeorectDone(self, **kargs):
  905. """Print final message"""
  906. global maptype
  907. if maptype == 'cell':
  908. return
  909. returncode = kargs['returncode']
  910. xyvpath = os.path.join(self.grassdatabase,
  911. self.xylocation,
  912. self.xymapset,
  913. 'vector',
  914. self.outname)
  915. vpath = os.path.join(self.grassdatabase,
  916. self.currentlocation,
  917. self.currentmapset,
  918. 'vector',
  919. self.outname)
  920. if os.path.isdir(vpath):
  921. self.parent.goutput.WriteWarning(_('Vector map <%s> already exists. '
  922. 'Change extension name and '
  923. 'georectify again.') % self.outname)
  924. else:
  925. if returncode == 0:
  926. if not os.path.isdir(os.path.join(self.grassdatabase,
  927. self.currentlocation,
  928. self.currentmapset,
  929. 'vector')):
  930. os.mkdir(os.path.join(self.grassdatabase,
  931. self.currentlocation,
  932. self.currentmapset,
  933. 'vector'))
  934. shutil.move(xyvpath, vpath)
  935. self.parent.goutput.WriteCmdLog(_('Vector map <%s> georectified '
  936. 'successfully') % self.outname)
  937. # copy attributes
  938. self.parent.goutput.WriteLog(_('Copying attributes...'))
  939. else:
  940. self.parent.goutput.WriteError(_('Georectification of vector map <%s> failed') %
  941. self.outname)
  942. del self.outname
  943. self.grwiz.SwitchEnv('original')
  944. def OnSettings(self, event):
  945. """Georectifier settings"""
  946. dlg = GrSettingsDialog(parent=self, id=wx.ID_ANY, title=_('Georectifier settings'))
  947. if dlg.ShowModal() == wx.ID_OK:
  948. pass
  949. dlg.Destroy()
  950. def OnQuit(self, event):
  951. """Quit georectifier"""
  952. self.grwiz.Cleanup()
  953. self.Destroy()
  954. event.Skip()
  955. def OnGRMethod(self, event):
  956. """
  957. sets transformation order for georectifying
  958. """
  959. if event:
  960. self.gr_order = event.GetInt() + 1
  961. numOfItems = self.list.GetItemCount()
  962. minNumOfItems = numOfItems
  963. if self.gr_order == 1:
  964. minNumOfItems = 3
  965. # self.SetStatusText(_('Insufficient points, 3+ points needed for 1st order'))
  966. elif self.gr_order == 2:
  967. minNumOfItems = 6
  968. diff = 6 - numOfItems
  969. # self.SetStatusText(_('Insufficient points, 6+ points needed for 2nd order'))
  970. elif self.gr_order == 3:
  971. minNumOfItems = 10
  972. # self.SetStatusText(_('Insufficient points, 10+ points needed for 3rd order'))
  973. for i in range(minNumOfItems - numOfItems):
  974. self.AddGCP(None)
  975. return minNumOfItems
  976. def RMSError(self, xygroup, order):
  977. """
  978. Uses g.transform to calculate forward and backward error for each used GCP
  979. in POINTS file and insert error values into GCP list.
  980. Calculates total forward and backward RMS error for all used points
  981. """
  982. # save GCPs to points file to make sure that all checked GCPs are used
  983. self.SaveGCPs(None)
  984. if self.CheckGCPcount(msg=True) == False:
  985. return
  986. # get list of forward and reverse rms error values for each point
  987. self.grwiz.SwitchEnv('new')
  988. ret = gcmd.RunCommand('g.transform',
  989. parent = self,
  990. read = True,
  991. group = xygroup,
  992. order = order)
  993. self.grwiz.SwitchEnv('original')
  994. if ret:
  995. errlist = ret.splitlines()
  996. if errlist == []:
  997. return
  998. # insert error values into GCP list for checked items
  999. i = 0
  1000. sumsq_fwd_err = 0.0
  1001. sumsq_bkw_err = 0.0
  1002. for index in range(self.list.GetItemCount()):
  1003. if self.list.IsChecked(index):
  1004. fwd_err, bkw_err = errlist[i].split()
  1005. self.list.SetStringItem(index, 4, fwd_err)
  1006. self.list.SetStringItem(index, 5, bkw_err)
  1007. sumsq_fwd_err += float(fwd_err)**2
  1008. sumsq_bkw_err += float(bkw_err)**2
  1009. i += 1
  1010. else:
  1011. self.list.SetStringItem(index, 4, '')
  1012. self.list.SetStringItem(index, 5, '')
  1013. # calculate RMS error
  1014. self.fwd_rmserror = round((sumsq_fwd_err/i)**0.5,4)
  1015. self.bkw_rmserror = round((sumsq_bkw_err/i)**0.5,4)
  1016. self.list.ResizeColumns()
  1017. self.SetStatusText(_('RMS error for selected points forward: %(fwd)s backward: %(bkw)s') % \
  1018. { 'fwd' : self.fwd_rmserror, 'bkw' : self.bkw_rmserror })
  1019. class GCPList(wx.ListCtrl,
  1020. CheckListCtrlMixin,
  1021. ListCtrlAutoWidthMixin):
  1022. def __init__(self, parent, gcp, id=wx.ID_ANY,
  1023. pos=wx.DefaultPosition, size=wx.DefaultSize,
  1024. style=wx.LC_REPORT | wx.SUNKEN_BORDER | wx.LC_HRULES |
  1025. wx.LC_SINGLE_SEL):
  1026. wx.ListCtrl.__init__(self, parent, id, pos, size, style)
  1027. # Mixin settings
  1028. CheckListCtrlMixin.__init__(self)
  1029. ListCtrlAutoWidthMixin.__init__(self)
  1030. # TextEditMixin.__init__(self)
  1031. self.gcp = gcp # GCP class
  1032. # tracks whether list items are checked or not
  1033. self.CheckList = []
  1034. self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected)
  1035. self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated)
  1036. self._Create()
  1037. self.selected = wx.NOT_FOUND
  1038. def _Create(self):
  1039. idx_col = 0
  1040. for col in (_('use| X coord'),
  1041. _('Y coord'),
  1042. _('E coord'),
  1043. _('N coord'),
  1044. _('Forward error'),
  1045. _('Backward error')):
  1046. self.InsertColumn(idx_col, col)
  1047. idx_col += 1
  1048. def LoadData(self):
  1049. """Load data into list"""
  1050. self.DeleteAllItems()
  1051. if os.path.isfile(self.gcp.file['points']):
  1052. self.gcp.ReadGCPs()
  1053. else:
  1054. # 3 gcp is minimum
  1055. for i in range(3):
  1056. self.gcp.AddGCP(None)
  1057. # select first point by default
  1058. self.selected = 0
  1059. self.SetItemState(self.selected,
  1060. wx.LIST_STATE_SELECTED,
  1061. wx.LIST_STATE_SELECTED)
  1062. self.ResizeColumns()
  1063. def OnCheckItem(self, index, flag):
  1064. """Item is checked/unchecked"""
  1065. pass
  1066. def AddGCPItem(self):
  1067. """
  1068. Appends an item to GCP list
  1069. """
  1070. self.Append(['0.0',
  1071. '0.0',
  1072. '0.0',
  1073. '0.0',
  1074. '',
  1075. ''])
  1076. self.selected = self.GetItemCount() - 1
  1077. self.SetItemState(self.selected,
  1078. wx.LIST_STATE_SELECTED,
  1079. wx.LIST_STATE_SELECTED)
  1080. self.ResizeColumns()
  1081. return self.selected
  1082. def DeleteGCPItem(self):
  1083. """
  1084. Deletes selected item in GCP list
  1085. """
  1086. if self.selected == wx.NOT_FOUND:
  1087. return
  1088. self.DeleteItem(self.selected)
  1089. if self.GetItemCount() > 0:
  1090. self.selected = self.GetItemCount() - 1
  1091. self.SetItemState(self.selected,
  1092. wx.LIST_STATE_SELECTED,
  1093. wx.LIST_STATE_SELECTED)
  1094. else:
  1095. self.selected = wx.NOT_FOUND
  1096. return self.selected
  1097. def ResizeColumns(self):
  1098. """Resize columns"""
  1099. minWidth = 90
  1100. for i in range(self.GetColumnCount()):
  1101. self.SetColumnWidth(i, wx.LIST_AUTOSIZE)
  1102. if self.GetColumnWidth(i) < minWidth:
  1103. self.SetColumnWidth(i, minWidth)
  1104. self.SendSizeEvent()
  1105. def GetSelected(self):
  1106. """Get index of selected item"""
  1107. return self.selected
  1108. def OnItemSelected(self, event):
  1109. self.selected = event.GetIndex()
  1110. def OnItemActivated(self, event):
  1111. """
  1112. When item double clicked, open editor to update coordinate values
  1113. """
  1114. coords = []
  1115. index = event.GetIndex()
  1116. for i in range(4):
  1117. coords.append(self.GetItem(index, i).GetText())
  1118. dlg = EditGPC(parent=self, id=wx.ID_ANY, data=coords)
  1119. if dlg.ShowModal() == wx.ID_OK:
  1120. values = dlg.GetValues() # string
  1121. if len(values) == 0:
  1122. wx.MessageBox(parent=self,
  1123. caption=_("Edit GCP"),
  1124. message=_("Invalid coordinate value. Operation cancelled."),
  1125. style=wx.CENTRE | wx.ICON_ERROR | wx.ID_OK)
  1126. else:
  1127. for i in range(len(values)):
  1128. if values[i] != coords[i]:
  1129. self.SetStringItem(index, i, values[i])
  1130. mapdisp = self.gcp.mapcoordlist[index]['gcpcoord'][2]
  1131. self.gcp.mapcoordlist[index]['gcpcoord'] = (float(values[0]), float(values[1]), mapdisp)
  1132. mapdisp = self.gcp.mapcoordlist[index]['mapcoord'][2]
  1133. self.gcp.mapcoordlist[index]['mapcoord'] = (float(values[0]), float(values[1]), mapdisp)
  1134. class VectGroup(wx.Dialog):
  1135. """
  1136. Dialog to create a vector group (VREF file) for georectifying
  1137. @todo Replace by g.group
  1138. """
  1139. def __init__(self, parent, id, grassdb, location, mapset, group,
  1140. style=wx.DEFAULT_DIALOG_STYLE):
  1141. wx.Dialog.__init__(self, parent, id, style=style,
  1142. title = _("Create vector map group"))
  1143. self.grassdatabase = grassdb
  1144. self.xylocation = location
  1145. self.xymapset = mapset
  1146. self.xygroup = group
  1147. #
  1148. # get list of valid vector directories
  1149. #
  1150. vectlist = os.listdir(os.path.join(self.grassdatabase,
  1151. self.xylocation,
  1152. self.xymapset,
  1153. 'vector'))
  1154. for dir in vectlist:
  1155. if not os.path.isfile(os.path.join(self.grassdatabase,
  1156. self.xylocation,
  1157. self.xymapset,
  1158. 'vector',
  1159. dir,
  1160. 'coor')):
  1161. vectlist.remove(dir)
  1162. utils.ListSortLower(vectlist)
  1163. # path to vref file
  1164. self.vgrpfile = os.path.join(self.grassdatabase,
  1165. self.xylocation,
  1166. self.xymapset,
  1167. 'group',
  1168. self.xygroup,
  1169. 'VREF')
  1170. #
  1171. # buttons
  1172. #
  1173. self.btnCancel = wx.Button(parent = self,
  1174. id = wx.ID_CANCEL)
  1175. self.btnOK = wx.Button(parent = self,
  1176. id = wx.ID_OK)
  1177. self.btnOK.SetDefault()
  1178. #
  1179. # list of vector maps
  1180. #
  1181. self.listMap = wx.CheckListBox(parent = self, id = wx.ID_ANY,
  1182. choices = vectlist)
  1183. if os.path.isfile(self.vgrpfile):
  1184. f = open(self.vgrpfile)
  1185. try:
  1186. checked = []
  1187. for line in f.readlines():
  1188. line = line.replace('\n', '')
  1189. if len(line) < 1:
  1190. continue
  1191. checked.append(line)
  1192. self.listMap.SetCheckedStrings(checked)
  1193. finally:
  1194. f.close()
  1195. line = wx.StaticLine(parent = self,
  1196. id = wx.ID_ANY, size = (20, -1),
  1197. style = wx.LI_HORIZONTAL)
  1198. #
  1199. # layout
  1200. #
  1201. sizer = wx.BoxSizer(wx.VERTICAL)
  1202. box = wx.BoxSizer(wx.HORIZONTAL)
  1203. box.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
  1204. label = _('Select vector map(s) to add to group:')),
  1205. flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.LEFT,
  1206. border = 5)
  1207. box.Add(item = self.listMap,
  1208. flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.LEFT,
  1209. border = 5)
  1210. sizer.Add(box, flag = wx.ALIGN_RIGHT | wx.ALL,
  1211. border = 3)
  1212. sizer.Add(item = line, proportion = 0,
  1213. flag = wx.GROW | wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
  1214. border = 5)
  1215. # buttons
  1216. btnSizer = wx.StdDialogButtonSizer()
  1217. btnSizer.AddButton(self.btnCancel)
  1218. btnSizer.AddButton(self.btnOK)
  1219. btnSizer.Realize()
  1220. sizer.Add(item = btnSizer, proportion = 0,
  1221. flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER,
  1222. border = 5)
  1223. self.SetSizer(sizer)
  1224. sizer.Fit(self)
  1225. self.Layout()
  1226. def MakeVGroup(self):
  1227. """Create VREF file"""
  1228. vgrouplist = []
  1229. for item in range(self.listMap.GetCount()):
  1230. if not self.listMap.IsChecked(item):
  1231. continue
  1232. vgrouplist.append(self.listMap.GetString(item))
  1233. f = open(self.vgrpfile, mode='w')
  1234. try:
  1235. for vect in vgrouplist:
  1236. f.write(vect + '\n')
  1237. finally:
  1238. f.close()
  1239. class EditGPC(wx.Dialog):
  1240. def __init__(self, parent, data, id=wx.ID_ANY,
  1241. title=_("Edit GCP"),
  1242. style=wx.DEFAULT_DIALOG_STYLE):
  1243. """Dialog for editing GPC and map coordinates in list control"""
  1244. wx.Dialog.__init__(self, parent, id, title=title, style=style)
  1245. panel = wx.Panel(parent=self)
  1246. sizer = wx.BoxSizer(wx.VERTICAL)
  1247. box = wx.StaticBox (parent=panel, id=wx.ID_ANY,
  1248. label=" %s " % _("Ground Control Point"))
  1249. boxSizer = wx.StaticBoxSizer(box, wx.VERTICAL)
  1250. # source coordinates
  1251. gridSizer = wx.GridBagSizer(vgap=5, hgap=5)
  1252. self.xcoord = wx.TextCtrl(parent=panel, id=wx.ID_ANY, size=(150, -1))
  1253. self.ycoord = wx.TextCtrl(parent=panel, id=wx.ID_ANY, size=(150, -1))
  1254. self.ncoord = wx.TextCtrl(parent=panel, id=wx.ID_ANY, size=(150, -1))
  1255. self.ecoord = wx.TextCtrl(parent=panel, id=wx.ID_ANY, size=(150, -1))
  1256. row = 0
  1257. col = 0
  1258. idx = 0
  1259. for label, win in ((_("X:"), self.xcoord),
  1260. (_("Y:"), self.ycoord),
  1261. (_("E:"), self.ecoord),
  1262. (_("N:"), self.ncoord)):
  1263. label = wx.StaticText(parent=panel, id=wx.ID_ANY,
  1264. label=label)
  1265. gridSizer.Add(item=label,
  1266. flag=wx.ALIGN_CENTER_VERTICAL,
  1267. pos=(row, col))
  1268. col += 1
  1269. win.SetValue(str(data[idx]))
  1270. gridSizer.Add(item=win,
  1271. pos=(row, col))
  1272. col += 1
  1273. idx += 1
  1274. if col > 3:
  1275. row += 1
  1276. col = 0
  1277. boxSizer.Add(item=gridSizer, proportion=1,
  1278. flag=wx.EXPAND | wx.ALL, border=5)
  1279. sizer.Add(item=boxSizer, proportion=1,
  1280. flag=wx.EXPAND | wx.ALL, border=5)
  1281. #
  1282. # buttons
  1283. #
  1284. self.btnCancel = wx.Button(panel, wx.ID_CANCEL)
  1285. self.btnOk = wx.Button(panel, wx.ID_OK)
  1286. self.btnOk.SetDefault()
  1287. btnSizer = wx.StdDialogButtonSizer()
  1288. btnSizer.AddButton(self.btnCancel)
  1289. btnSizer.AddButton(self.btnOk)
  1290. btnSizer.Realize()
  1291. sizer.Add(item=btnSizer, proportion=0,
  1292. flag=wx.ALIGN_RIGHT | wx.ALL, border=5)
  1293. panel.SetSizer(sizer)
  1294. sizer.Fit(self)
  1295. def GetValues(self, columns=None):
  1296. """Return list of values (as strings).
  1297. """
  1298. valuelist = []
  1299. try:
  1300. float(self.xcoord.GetValue())
  1301. float(self.ycoord.GetValue())
  1302. float(self.ecoord.GetValue())
  1303. float(self.ncoord.GetValue())
  1304. except ValueError:
  1305. return valuelist
  1306. valuelist.append(self.xcoord.GetValue())
  1307. valuelist.append(self.ycoord.GetValue())
  1308. valuelist.append(self.ecoord.GetValue())
  1309. valuelist.append(self.ncoord.GetValue())
  1310. return valuelist
  1311. class GrSettingsDialog(wx.Dialog):
  1312. def __init__(self, parent, id, title, pos=wx.DefaultPosition, size=wx.DefaultSize,
  1313. style=wx.DEFAULT_DIALOG_STYLE):
  1314. wx.Dialog.__init__(self, parent, id, title, pos, size, style)
  1315. """
  1316. Dialog to set profile text options: font, title
  1317. and font size, axis labels and font size
  1318. """
  1319. #
  1320. # initialize variables
  1321. #
  1322. self.parent = parent
  1323. self.symbol = {}
  1324. self._do_layout()
  1325. def _do_layout(self):
  1326. """Do layout"""
  1327. # dialog layout
  1328. sizer = wx.BoxSizer(wx.VERTICAL)
  1329. box = wx.StaticBox(parent=self, id=wx.ID_ANY,
  1330. label=" %s " % _("Symbol settings"))
  1331. boxSizer = wx.StaticBoxSizer(box, wx.VERTICAL)
  1332. gridSizer = wx.GridBagSizer(vgap=5, hgap=5)
  1333. gridSizer.AddGrowableCol(1)
  1334. #
  1335. # symbol color
  1336. #
  1337. row = 0
  1338. label = wx.StaticText(parent=self, id=wx.ID_ANY, label=_("Color:"))
  1339. gridSizer.Add(item=label, flag=wx.ALIGN_CENTER_VERTICAL, pos=(row, 0))
  1340. col = UserSettings.Get(group='georect', key='symbol', subkey='color')
  1341. colWin = csel.ColourSelect(parent=self, id=wx.ID_ANY,
  1342. colour=wx.Colour(col[0],
  1343. col[1],
  1344. col[2],
  1345. 255))
  1346. self.symbol['color'] = colWin.GetId()
  1347. gridSizer.Add(item=colWin,
  1348. flag=wx.ALIGN_RIGHT,
  1349. pos=(row, 1))
  1350. #
  1351. # symbol width
  1352. #
  1353. row += 1
  1354. label = wx.StaticText(parent=self, id=wx.ID_ANY, label=_("Width:"))
  1355. gridSizer.Add(item=label, flag=wx.ALIGN_CENTER_VERTICAL, pos=(row, 0))
  1356. width = int(UserSettings.Get(group='georect', key='symbol', subkey='width'))
  1357. widWin = wx.SpinCtrl(parent=self, id=wx.ID_ANY,
  1358. min=1, max=10)
  1359. widWin.SetValue(width)
  1360. self.symbol['width'] = widWin.GetId()
  1361. gridSizer.Add(item=widWin,
  1362. flag=wx.ALIGN_RIGHT,
  1363. pos=(row, 1))
  1364. boxSizer.Add(item=gridSizer, flag=wx.EXPAND)
  1365. sizer.Add(item=boxSizer, flag=wx.EXPAND | wx.ALL, border=5)
  1366. line = wx.StaticLine(parent=self, id=wx.ID_ANY, size=(20, -1), style=wx.LI_HORIZONTAL)
  1367. sizer.Add(item=line, proportion=0,
  1368. flag=wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.LEFT|wx.RIGHT, border=3)
  1369. #
  1370. # buttons
  1371. #
  1372. btnSave = wx.Button(self, wx.ID_SAVE)
  1373. btnApply = wx.Button(self, wx.ID_APPLY)
  1374. btnCancel = wx.Button(self, wx.ID_CANCEL)
  1375. btnSave.SetDefault()
  1376. # bindigs
  1377. btnApply.Bind(wx.EVT_BUTTON, self.OnApply)
  1378. btnApply.SetToolTipString(_("Apply changes for the current session"))
  1379. btnSave.Bind(wx.EVT_BUTTON, self.OnSave)
  1380. btnSave.SetToolTipString(_("Apply and save changes to user settings file (default for next sessions)"))
  1381. btnSave.SetDefault()
  1382. btnCancel.Bind(wx.EVT_BUTTON, self.OnCancel)
  1383. btnCancel.SetToolTipString(_("Close dialog and ignore changes"))
  1384. # sizers
  1385. btnStdSizer = wx.StdDialogButtonSizer()
  1386. btnStdSizer.AddButton(btnCancel)
  1387. btnStdSizer.AddButton(btnSave)
  1388. btnStdSizer.AddButton(btnApply)
  1389. btnStdSizer.Realize()
  1390. sizer.Add(item=btnStdSizer, proportion=0, flag=wx.ALIGN_RIGHT | wx.ALL, border=5)
  1391. self.SetSizer(sizer)
  1392. sizer.Fit(self)
  1393. def UpdateSettings(self):
  1394. UserSettings.Set(group='georect', key='symbol', subkey='color',
  1395. value=wx.FindWindowById(self.symbol['color']).GetColour())
  1396. UserSettings.Set(group='georect', key='symbol', subkey='width',
  1397. value=wx.FindWindowById(self.symbol['width']).GetValue())
  1398. def OnSave(self, event):
  1399. """Button 'Save' pressed"""
  1400. self.UpdateSettings()
  1401. fileSettings = {}
  1402. UserSettings.ReadSettingsFile(settings=fileSettings)
  1403. fileSettings['georect'] = UserSettings.Get(group='georect')
  1404. file = UserSettings.SaveToFile(fileSettings)
  1405. self.parent.parent.goutput.WriteLog(_('Georectifier settings saved to file \'%s\'.') % file)
  1406. self.Close()
  1407. def OnApply(self, event):
  1408. """Button 'Apply' pressed"""
  1409. self.UpdateSettings()
  1410. self.Close()
  1411. def OnCancel(self, event):
  1412. """Button 'Cancel' pressed"""
  1413. self.Close()