1"""
2@package photo2image.ip2i_manager
3
4@brief Scanning distortion correction of a photo for GRASS GIS.
5Includes ground control point management and interactive point
6and click GCP creation
7
8Classes:
9 - ip2i_manager::GCPWizard
10 - ip2i_manager::GCP
11 - ip2i_manager::GCPList
12 - ip2i_manager::EditGCP
13 - ip2i_manager::GrSettingsDialog
14
15(C) 2006-2017 by the GRASS Development Team
16
17This program is free software under the GNU General Public License
18(>=v2). Read the file COPYING that comes with GRASS for details.
19
20@author Original author Michael Barton
21@author Original version improved by Martin Landa <landa.martin gmail.com>
22@author Rewritten by Markus Metz redesign georectfier -> GCP Manage
23@author Support for GraphicsSet added by Stepan Turek <stepan.turek seznam.cz> (2012)
24@author Yann modified: graphical replacement of i.photo.2image (was in v6 using Vask lib)
25"""
26
27from __future__ import print_function
28
29import os
30import sys
31import six
32import shutil
33from copy import copy
34
35import wx
36from wx.lib.mixins.listctrl import ColumnSorterMixin, ListCtrlAutoWidthMixin
37import wx.lib.colourselect as csel
38
39import grass.script as grass
40
41from core import utils, globalvar
42from core.render import Map
43from gui_core.gselect import Select, LocationSelect, MapsetSelect
44from gui_core.dialogs import GroupDialog
45from core.gcmd import RunCommand, GMessage, GError, GWarning
46from core.settings import UserSettings
47from photo2image.ip2i_mapdisplay import MapFrame
48from core.giface import Notification
49from gui_core.wrap import SpinCtrl, Button, StaticText, StaticBox, \
50    TextCtrl, Menu, ListCtrl, BitmapFromImage, CheckListCtrlMixin
51
52from location_wizard.wizard import TitledPage as TitledPage
53
54#
55# global variables
56#
57global src_map
58global tgt_map
59global maptype
60
61src_map = ''
62tgt_map = ''
63maptype = 'raster'
64
65
66def getSmallUpArrowImage():
67    stream = open(os.path.join(globalvar.IMGDIR, 'small_up_arrow.png'), 'rb')
68    try:
69        img = wx.Image(stream)
70    finally:
71        stream.close()
72    return img
73
74
75def getSmallDnArrowImage():
76    stream = open(os.path.join(globalvar.IMGDIR, 'small_down_arrow.png'), 'rb')
77    try:
78        img = wx.Image(stream)
79    finally:
80        stream.close()
81    stream.close()
82    return img
83
84
85class GCPWizard(object):
86    """
87    Not a wizard anymore
88    """
89
90    def __init__(self, parent, giface, group, raster, raster1, camera, order, extension):
91        global maptype
92        global src_map
93        global tgt_map
94        maptype = 'raster'
95        rendertype = 'raster'
96        self.parent = parent  # GMFrame
97        self._giface = giface
98        self.group = group
99        self.src_map = raster
100        self.tgt_map = raster1
101        self.camera = camera
102        self.order = int(order)
103        self.extension = extension
104        self.src_maps=self.src_map
105
106        #
107        # get environmental variables
108        #
109        self.grassdatabase = grass.gisenv()['GISDBASE']
110
111        #
112        # read original environment settings
113        #
114        self.target_gisrc = os.environ['GISRC']
115        self.source_gisrc = os.environ['GISRC']
116        self.gisrc_dict = {}
117        try:
118            f = open(self.target_gisrc, 'r')
119            for line in f.readlines():
120                line = line.replace('\n', '').strip()
121                if len(line) < 1:
122                    continue
123                key, value = line.split(':', 1)
124                self.gisrc_dict[key.strip()] = value.strip()
125        finally:
126            f.close()
127
128        self.currentlocation = self.gisrc_dict['LOCATION_NAME']
129        self.currentmapset = self.gisrc_dict['MAPSET']
130        # location for xy map to georectify
131        self.newlocation = self.currentlocation
132        self.xylocation = self.currentlocation
133        # mapset for xy map to georectify
134        self.newmapset = self.currentmapset
135        self.xymapset = self.currentmapset
136        # get group name from command line
137        self.xygroup = self.group
138
139        # GISRC file for source location/mapset of map(s) to georectify
140        self.SetSrcEnv(self.currentlocation,self.currentmapset)
141
142        #
143        # start GCP display
144        #
145        # instance of render.Map to be associated with display
146        self.SwitchEnv('source')
147        self.SrcMap = Map(gisrc=self.source_gisrc)
148        self.SwitchEnv('target')
149        self.TgtMap = Map(gisrc=self.target_gisrc)
150        self.Map = self.SrcMap
151
152        #
153        # add layer to source map
154        #
155        try:
156            # set computational region to match selected map and zoom display
157            # to region
158            p = RunCommand('g.region', 'raster='+self.src_map)
159
160            if p.returncode == 0:
161                print('returncode = ', str(p.returncode))
162                self.Map.region = self.Map.GetRegion()
163        except:
164            pass
165
166        self.SwitchEnv('source')
167        cmdlist = ['d.rast', 'map=%s' % self.src_map]
168        name, found = utils.GetLayerNameFromCmd(cmdlist)
169        self.SrcMap.AddLayer(
170            ltype=rendertype,
171            command=cmdlist,
172            active=True,
173            name=name,
174            hidden=False,
175            opacity=1.0,
176            render=False)
177
178        #
179        # add raster layer to target map
180        #
181        self.SwitchEnv('target')
182        cmdlist = ['d.rast', 'map=%s' % self.tgt_map]
183        name, found = utils.GetLayerNameFromCmd(cmdlist)
184        self.TgtMap.AddLayer(
185            ltype=rendertype,
186            command=cmdlist,
187            active=True,
188            name=name,
189            hidden=False,
190            opacity=1.0,
191            render=False)
192
193        #
194        # start GCP Manager
195        #
196        self.gcpmgr = GCP(self.parent, giface=self._giface,
197                        grwiz=self, size=globalvar.MAP_WINDOW_SIZE,
198                        toolbars=["gcpdisp"],
199                        Map=self.SrcMap, lmgr=self.parent, camera=camera)
200
201        # load GCPs
202        self.gcpmgr.InitMapDisplay()
203        self.gcpmgr.CenterOnScreen()
204        self.gcpmgr.Show()
205        # need to update AUI here for wingrass
206        self.gcpmgr._mgr.Update()
207        self.SwitchEnv('target')
208
209    def SetSrcEnv(self, location, mapset):
210        """Create environment to use for location and mapset
211        that are the source of the file(s) to georectify
212
213        :param location: source location
214        :param mapset: source mapset
215
216        :return: False on error
217        :return: True on success
218        """
219
220        self.newlocation = location
221        self.newmapset = mapset
222
223        # check to see if we are georectifying map in current working
224        # location/mapset
225        if self.newlocation == self.currentlocation and self.newmapset == self.currentmapset:
226            return False
227
228        self.gisrc_dict['LOCATION_NAME'] = location
229        self.gisrc_dict['MAPSET'] = mapset
230
231        self.source_gisrc = utils.GetTempfile()
232
233        try:
234            f = open(self.source_gisrc, mode='w')
235            for line in self.gisrc_dict.items():
236                f.write(line[0] + ": " + line[1] + "\n")
237        finally:
238            f.close()
239
240        return True
241
242    def SwitchEnv(self, grc):
243        """
244        Switches between original working location/mapset and
245        location/mapset that is source of file(s) to georectify
246        """
247        # check to see if we are georectifying map in current working
248        # location/mapset
249        if self.newlocation == self.currentlocation and self.newmapset == self.currentmapset:
250            return False
251
252        if grc == 'target':
253            os.environ['GISRC'] = str(self.target_gisrc)
254        elif grc == 'source':
255            os.environ['GISRC'] = str(self.source_gisrc)
256
257        return True
258
259    def OnGLMFocus(self, event):
260        """Layer Manager focus"""
261        # self.SwitchEnv('target')
262
263        event.Skip()
264
265class GCP(MapFrame, ColumnSorterMixin):
266    """
267    Manages ground control points for georectifying. Calculates RMS statistics.
268    Calls i.rectify or v.rectify to georectify map.
269    """
270
271    def __init__(self, parent, giface, grwiz=None, id=wx.ID_ANY,
272                 title=_("Manage Location of Fiducial Points on a Scanned Photo"),
273                 size=(700, 300), toolbars=["gcpdisp"], Map=None, lmgr=None, camera=None):
274
275        self.grwiz = grwiz  # GR Wizard
276        self._giface = giface
277
278        if tgt_map == '':
279            self.show_target = False
280        else:
281            self.show_target = True
282
283        self.camera = camera
284
285        #wx.Frame.__init__(self, parent, id, title, size = size, name = "GCPFrame")
286        MapFrame.__init__(
287            self,
288            parent=parent,
289            giface=self._giface,
290            title=title,
291            size=size,
292            Map=Map,
293            toolbars=toolbars,
294            name='GCPMapWindow')
295
296        # init variables
297        self.parent = parent
298
299        #
300        # register data structures for drawing GCP's
301        #
302        self.pointsToDrawTgt = self.TgtMapWindow.RegisterGraphicsToDraw(
303            graphicsType="point", setStatusFunc=self.SetGCPSatus)
304        self.pointsToDrawSrc = self.SrcMapWindow.RegisterGraphicsToDraw(
305            graphicsType="point", setStatusFunc=self.SetGCPSatus)
306
307        # connect to the map windows signals
308        # used to add or edit GCP
309        self.SrcMapWindow.mouseLeftUpPointer.connect(
310            lambda x, y:
311            self._onMouseLeftUpPointer(self.SrcMapWindow, x, y))
312        self.TgtMapWindow.mouseLeftUpPointer.connect(
313            lambda x, y:
314            self._onMouseLeftUpPointer(self.TgtMapWindow, x, y))
315
316        # window resized
317        self.resize = False
318
319        self.grassdatabase = self.grwiz.grassdatabase
320
321        self.currentlocation = self.grwiz.currentlocation
322        self.currentmapset = self.grwiz.currentmapset
323
324        self.newlocation = self.grwiz.newlocation
325        self.newmapset = self.grwiz.newmapset
326
327        self.xylocation = self.grwiz.gisrc_dict['LOCATION_NAME']
328        self.xymapset = self.grwiz.gisrc_dict['MAPSET']
329        self.xygroup = self.grwiz.xygroup.split("@")[0]
330        self.src_maps = self.grwiz.src_maps
331        self.extension = self.grwiz.extension
332        self.outname = ''
333
334        self.file = {
335            'camera': os.path.join(self.grassdatabase,
336                                   self.xylocation,
337                                   self.xymapset,
338                                   'camera',
339                                   self.camera),
340            'ref_points': os.path.join(self.grassdatabase,
341                                   self.xylocation,
342                                   self.xymapset,
343                                   'group',
344                                   self.xygroup,
345                                   'REF_POINTS'),
346            'points': os.path.join(self.grassdatabase,
347                                   self.xylocation,
348                                   self.xymapset,
349                                   'group',
350                                   self.xygroup,
351                                   'POINTS'),
352            'points_bak': os.path.join(self.grassdatabase,
353                                   self.xylocation,
354                                   self.xymapset,
355                                   'group',
356                                   self.xygroup,
357                                   'POINTS_BAK'),
358            'rgrp': os.path.join(self.grassdatabase,
359                                   self.xylocation,
360                                   self.xymapset,
361                                   'group',
362                                   self.xygroup,
363                                   'REF'),
364            'target': os.path.join(self.grassdatabase,
365                                   self.xylocation,
366                                   self.xymapset,
367                                   'group',
368                                   self.xygroup,
369                                   'TARGET'),
370        }
371
372        # make a backup of the current points file if exists
373        if os.path.exists(self.file['points']):
374            shutil.copy(self.file['points'], self.file['points_bak'])
375            shutil.copy(self.file['points'], self.file['ref_points'])
376            GMessage (_("A POINTS file exists, renaming it to POINTS_BAK"))
377
378        #"""Make a POINTS file """
379        import re,sys
380        try:
381            fc = open(self.file['camera'], mode='r')
382            fc_count=0
383            for line in fc:
384                fc_count+=1
385                if re.search("NUM", line):
386                    storeLine=fc_count
387                    numberOfFiducial = int(line.split()[-1])
388            dataFiducialX=[]
389            dataFiducialY=[]
390            fc = open(self.file['camera'], mode='r')
391            fc_count=0
392            for line in fc:
393                fc_count+=1
394                if fc_count > storeLine :
395                    dataFiducialX.append(line.split()[1])
396                    dataFiducialY.append(line.split()[2])
397
398        except IOError as err:
399            GError(
400                parent=self,
401                message="%s <%s>. %s%s" %
402                (_("Opening CAMERA file failed"),
403                 self.file['camera'],
404                    os.linesep,
405                    err))
406            return
407
408        self.GCPcount = 0
409        try:
410            f = open(self.file['points'], mode='w')
411            # use os.linesep or '\n' here ???
412            f.write('# Ground Control Points File\n')
413            f.write("# \n")
414            f.write("# target location: " + self.currentlocation + '\n')
415            f.write("# target mapset: " + self.currentmapset + '\n')
416            f.write("#\tsource\t\ttarget\t\tstatus\n")
417            f.write("#\teast\tnorth\teast\tnorth\t(1=ok, 0=ignore)\n")
418            f.write(
419                "#-----------------------     -----------------------     ---------------\n")
420
421            check = "0"
422            for index in range(numberOfFiducial):
423                coordX0 = "0"
424                coordY0 = "0"
425                coordX1 = dataFiducialX[index]
426                coordY1 = dataFiducialY[index]
427                f.write(coordX0 + ' ' +
428                        coordY0 + '     ' +
429                        coordX1 + ' ' +
430                        coordY1 + '     ' +
431                        check + '\n')
432
433        except IOError as err:
434            GError(
435                parent=self,
436                message="%s <%s>. %s%s" %
437                (_("Writing POINTS file failed"),
438                 self.file['points'],
439                    os.linesep,
440                    err))
441            return
442
443        f.close()
444
445        # polynomial order transformation for georectification
446        self.gr_order = self.grwiz.order
447        # interpolation method for georectification
448        self.gr_method = 'nearest'
449        # region clipping for georectified map
450        self.clip_to_region = False
451        # number of GCPs selected to be used for georectification (checked)
452        self.GCPcount = 0
453        # forward RMS error
454        self.fwd_rmserror = 0.0
455        # backward RMS error
456        self.bkw_rmserror = 0.0
457        # list map coords and ID of map display they came from
458        self.mapcoordlist = []
459        self.mapcoordlist.append([0,        # GCP number
460                                  0.0,      # source east
461                                  0.0,      # source north
462                                  0.0,      # target east
463                                  0.0,      # target north
464                                  0.0,      # forward error
465                                  0.0])   # backward error
466
467        # init vars to highlight high RMS errors
468        self.highest_only = True
469        self.show_unused = True
470        self.highest_key = -1
471        self.rmsthresh = 0
472        self.rmsmean = 0
473        self.rmssd = 0
474
475        self.SetTarget(self.xygroup, self.currentlocation, self.currentmapset)
476
477        self.itemDataMap = None
478
479        # images for column sorting
480        # CheckListCtrlMixin must set an ImageList first
481        self.il = self.list.GetImageList(wx.IMAGE_LIST_SMALL)
482
483        SmallUpArrow = BitmapFromImage(getSmallUpArrowImage())
484        SmallDnArrow = BitmapFromImage(getSmallDnArrowImage())
485        self.sm_dn = self.il.Add(SmallDnArrow)
486        self.sm_up = self.il.Add(SmallUpArrow)
487
488        # set mouse characteristics
489        self.mapwin = self.SrcMapWindow
490        self.mapwin.mouse['box'] = 'point'
491        self.mapwin.mouse["use"] == "pointer"
492        self.mapwin.zoomtype = 0
493        self.mapwin.pen = wx.Pen(colour='black', width=2, style=wx.SOLID)
494        self.mapwin.SetNamedCursor('cross')
495
496        self.mapwin = self.TgtMapWindow
497
498        # set mouse characteristics
499        self.mapwin.mouse['box'] = 'point'
500        self.mapwin.mouse["use"] == "pointer"
501        self.mapwin.zoomtype = 0
502        self.mapwin.pen = wx.Pen(colour='black', width=2, style=wx.SOLID)
503        self.mapwin.SetNamedCursor('cross')
504
505        #
506        # show new display & draw map
507        #
508        if self.show_target:
509            self.MapWindow = self.TgtMapWindow
510            self.Map = self.TgtMap
511            self.OnZoomToMap(None)
512
513        self.MapWindow = self.SrcMapWindow
514        self.Map = self.SrcMap
515        self.OnZoomToMap(None)
516
517        #
518        # bindings
519        #
520        self.Bind(wx.EVT_ACTIVATE, self.OnFocus)
521        self.Bind(wx.EVT_SIZE, self.OnSize)
522        self.Bind(wx.EVT_IDLE, self.OnIdle)
523        self.Bind(wx.EVT_CLOSE, self.OnQuit)
524
525        self.SetSettings()
526
527    def __del__(self):
528        """Disable GCP manager mode"""
529        # leaving the method here but was used only to delete gcpmanagement
530        # from layer manager which is now not needed
531        pass
532
533    def CreateGCPList(self):
534        """Create GCP List Control"""
535
536        return GCPList(parent=self, gcp=self)
537
538    # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py
539    def GetListCtrl(self):
540        return self.list
541
542    def GetMapCoordList(self):
543        return self.mapcoordlist
544
545    # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py
546    def GetSortImages(self):
547        return (self.sm_dn, self.sm_up)
548
549    def GetFwdError(self):
550        return self.fwd_rmserror
551
552    def GetBkwError(self):
553        return self.bkw_rmserror
554
555    def InitMapDisplay(self):
556        self.list.LoadData()
557
558        # initialize column sorter
559        self.itemDataMap = self.mapcoordlist
560        ncols = self.list.GetColumnCount()
561        ColumnSorterMixin.__init__(self, ncols)
562        # init to ascending sort on first click
563        self._colSortFlag = [1] * ncols
564
565    def SetTarget(self, tgroup, tlocation, tmapset):
566        """
567        Sets rectification target to current location and mapset
568        """
569        # check to see if we are georectifying map in current working
570        # location/mapset
571        if self.newlocation == self.currentlocation and self.newmapset == self.currentmapset:
572            RunCommand('i.target',
573                       parent=self,
574                       flags='c',
575                       group=tgroup)
576        else:
577            self.grwiz.SwitchEnv('source')
578            RunCommand('i.target',
579                       parent=self,
580                       group=tgroup,
581                       location=tlocation,
582                       mapset=tmapset)
583            self.grwiz.SwitchEnv('target')
584
585    def AddGCP(self, event):
586        """
587        Appends an item to GCP list
588        """
589        keyval = self.list.AddGCPItem() + 1
590        # source east, source north, target east, target north, forward error,
591        # backward error
592        self.mapcoordlist.append([keyval,             # GCP number
593                                  0.0,                # source east
594                                  0.0,                # source north
595                                  0.0,                # target east
596                                  0.0,                # target north
597                                  0.0,                # forward error
598                                  0.0])             # backward error
599
600        if self.statusbarManager.GetMode() == 8:  # go to
601            self.StatusbarUpdate()
602
603    def DeleteGCP(self, event):
604        """
605        Deletes selected item in GCP list
606        """
607        minNumOfItems = self.OnGROrder(None)
608
609        if self.list.GetItemCount() <= minNumOfItems:
610            GMessage(
611                parent=self,
612                message=_("At least %d GCPs required. Operation canceled.") %
613                minNumOfItems)
614            return
615
616        key = self.list.DeleteGCPItem()
617        del self.mapcoordlist[key]
618
619        # update key and GCP number
620        for newkey in range(key, len(self.mapcoordlist)):
621            index = self.list.FindItem(-1, newkey + 1)
622            self.mapcoordlist[newkey][0] = newkey
623            self.list.SetItem(index, 0, str(newkey))
624            self.list.SetItemData(index, newkey)
625
626        # update selected
627        if self.list.GetItemCount() > 0:
628            if self.list.selected < self.list.GetItemCount():
629                self.list.selectedkey = self.list.GetItemData(
630                    self.list.selected)
631            else:
632                self.list.selected = self.list.GetItemCount() - 1
633                self.list.selectedkey = self.list.GetItemData(
634                    self.list.selected)
635
636            self.list.SetItemState(self.list.selected,
637                                   wx.LIST_STATE_SELECTED,
638                                   wx.LIST_STATE_SELECTED)
639        else:
640            self.list.selected = wx.NOT_FOUND
641            self.list.selectedkey = -1
642
643        self.UpdateColours()
644
645        if self.statusbarManager.GetMode() == 8:  # go to
646            self.StatusbarUpdate()
647            if self.list.selectedkey > 0:
648                self.statusbarManager.SetProperty(
649                    'gotoGCP', self.list.selectedkey)
650
651    def ClearGCP(self, event):
652        """
653        Clears all values in selected item of GCP list and unchecks it
654        """
655        index = self.list.GetSelected()
656        key = self.list.GetItemData(index)
657
658        for i in range(1, 5):
659            self.list.SetItem(index, i, '0.0')
660        self.list.SetItem(index, 5, '')
661        self.list.SetItem(index, 6, '')
662        self.list.CheckItem(index, False)
663
664        # GCP number, source E, source N, target E, target N, fwd error, bkwd
665        # error
666        self.mapcoordlist[key] = [key, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
667
668    def SetSettings(self):
669        """Sets settings for drawing of GCP's.
670        """
671        self.highest_only = UserSettings.Get(
672            group='gcpman', key='rms', subkey='highestonly')
673        self.show_unused = UserSettings.Get(
674            group='gcpman', key='symbol', subkey='unused')
675
676        colours = {"color": "default",
677                   "hcolor": "highest",
678                   "scolor": "selected",
679                   "ucolor": "unused"}
680        wpx = UserSettings.Get(group='gcpman', key='symbol', subkey='width')
681
682        for k, v in six.iteritems(colours):
683            col = UserSettings.Get(group='gcpman', key='symbol', subkey=k)
684            self.pointsToDrawSrc.GetPen(v).SetColour(wx.Colour(
685                col[0], col[1], col[2], 255))  # TODO GetPen neni to spatne?
686            self.pointsToDrawTgt.GetPen(v).SetColour(
687                wx.Colour(col[0], col[1], col[2], 255))
688
689            self.pointsToDrawSrc.GetPen(v).SetWidth(wpx)
690            self.pointsToDrawTgt.GetPen(v).SetWidth(wpx)
691
692        spx = UserSettings.Get(group='gcpman', key='symbol', subkey='size')
693        self.pointsToDrawSrc.SetPropertyVal("size", int(spx))
694        self.pointsToDrawTgt.SetPropertyVal("size", int(spx))
695
696        font = self.GetFont()
697        font.SetPointSize(int(spx) + 2)
698
699        textProp = {}
700        textProp['active'] = True
701        textProp['font'] = font
702        self.pointsToDrawSrc.SetPropertyVal("text", textProp)
703        self.pointsToDrawTgt.SetPropertyVal("text", copy(textProp))
704
705    def SetGCPSatus(self, item, itemIndex):
706        """Before GCP is drawn, decides it's colour and whether it
707        will be drawed.
708        """
709        key = self.list.GetItemData(itemIndex)
710        # incremented because of itemDataMap (has one more item) - will be
711        # changed
712        itemIndex += 1
713
714        if not self.list.IsItemChecked(key - 1):
715            wxPen = "unused"
716            if not self.show_unused:
717                item.SetPropertyVal('hide', True)
718            else:
719                item.SetPropertyVal('hide', False)
720
721        else:
722            item.SetPropertyVal('hide', False)
723            if self.highest_only == True:
724                if itemIndex == self.highest_key:
725                    wxPen = "highest"
726                else:
727                    wxPen = "default"
728            else:
729                if (self.mapcoordlist[key][5] > self.rmsthresh):
730                    wxPen = "highest"
731                else:
732                    wxPen = "default"
733
734        if itemIndex == self.list.selectedkey:
735            wxPen = "selected"
736
737        item.SetPropertyVal('label', str(itemIndex))
738        item.SetPropertyVal('penName', wxPen)
739
740    def SetGCPData(self, coordtype, coord, mapdisp=None, confirm=False):
741        """Inserts coordinates from file, mouse click on map, or
742        after editing into selected item of GCP list and checks it for
743        use.
744        """
745        index = self.list.GetSelected()
746        if index == wx.NOT_FOUND:
747            return
748
749        coord0 = coord[0]
750        coord1 = coord[1]
751
752        key = self.list.GetItemData(index)
753        if confirm:
754            if self.MapWindow == self.SrcMapWindow:
755                currloc = _("source")
756            else:
757                currloc = _("target")
758            ret = wx.MessageBox(
759                parent=self, caption=_("Set GCP coordinates"),
760                message=_(
761                    'Set %(coor)s coordinates for GCP No. %(key)s? \n\n'
762                    'East: %(coor0)s \n'
763                    'North: %(coor1)s') %
764                {'coor': currloc, 'key': str(key),
765                 'coor0': str(coord0),
766                 'coor1': str(coord1)},
767                style=wx.ICON_QUESTION | wx.YES_NO | wx.CENTRE)
768
769            # for wingrass
770            if os.name == 'nt':
771                self.MapWindow.SetFocus()
772            if ret == wx.NO:
773                return
774
775        if coordtype == 'source':
776            self.list.SetItem(index, 1, str(coord0))
777            self.list.SetItem(index, 2, str(coord1))
778            self.mapcoordlist[key][1] = coord[0]
779            self.mapcoordlist[key][2] = coord[1]
780            self.pointsToDrawSrc.GetItem(key - 1).SetCoords([coord0, coord1])
781
782        elif coordtype == 'target':
783            self.list.SetItem(index, 3, str(coord0))
784            self.list.SetItem(index, 4, str(coord1))
785            self.mapcoordlist[key][3] = coord[0]
786            self.mapcoordlist[key][4] = coord[1]
787            self.pointsToDrawTgt.GetItem(key - 1).SetCoords([coord0, coord1])
788
789        self.list.SetItem(index, 5, '0')
790        self.list.SetItem(index, 6, '0')
791        self.mapcoordlist[key][5] = 0.0
792        self.mapcoordlist[key][6] = 0.0
793
794        # self.list.ResizeColumns()
795
796    def SaveGCPs(self, event):
797        """Make a POINTS file or save GCP coordinates to existing
798        POINTS file
799        """
800        self.GCPcount = 0
801        try:
802            f = open(self.file['points'], mode='w')
803            # use os.linesep or '\n' here ???
804            f.write('# Ground Control Points File\n')
805            f.write("# \n")
806            f.write("# target location: " + self.currentlocation + '\n')
807            f.write("# target mapset: " + self.currentmapset + '\n')
808            f.write("#\tsource\t\ttarget\t\tstatus\n")
809            f.write("#\teast\tnorth\teast\tnorth\t(1=ok, 0=ignore)\n")
810            f.write(
811                "#-----------------------     -----------------------     ---------------\n")
812
813            for index in range(self.list.GetItemCount()):
814                if self.list.IsItemChecked(index):
815                    check = "1"
816                    self.GCPcount += 1
817                else:
818                    check = "0"
819                coord0 = self.list.GetItem(index, 1).GetText()
820                coord1 = self.list.GetItem(index, 2).GetText()
821                coord2 = self.list.GetItem(index, 3).GetText()
822                coord3 = self.list.GetItem(index, 4).GetText()
823                f.write(
824                    coord0 +
825                    ' ' +
826                    coord1 +
827                    '     ' +
828                    coord2 +
829                    ' ' +
830                    coord3 +
831                    '     ' +
832                    check +
833                    '\n')
834
835        except IOError as err:
836            GError(
837                parent=self,
838                message="%s <%s>. %s%s" %
839                (_("Writing POINTS file failed"),
840                 self.file['points'],
841                    os.linesep,
842                    err))
843            return
844
845        f.close()
846
847        # if event != None save also to backup file
848        if event:
849            shutil.copy(self.file['points'], self.file['points_bak'])
850            shutil.copy(self.file['points'], self.file['ref_points'])
851            self._giface.WriteLog(
852                _('POINTS file saved for group <%s>') %
853                self.xygroup)
854            #self.SetStatusText(_('POINTS file saved'))
855
856    def ReadGCPs(self):
857        """
858        Reads GCPs and georectified coordinates from POINTS file
859        """
860
861        self.GCPcount = 0
862
863        sourceMapWin = self.SrcMapWindow
864        targetMapWin = self.TgtMapWindow
865
866        if not sourceMapWin:
867            GError(parent=self,
868                   message="%s. %s%s" % (_("source mapwin not defined"),
869                                         os.linesep, err))
870
871        if not targetMapWin:
872            GError(parent=self,
873                   message="%s. %s%s" % (_("target mapwin not defined"),
874                                         os.linesep, err))
875
876        try:
877            f = open(self.file['points'], 'r')
878            GCPcnt = 0
879
880            for line in f.readlines():
881                if line[0] == '#' or line == '':
882                    continue
883                line = line.replace('\n', '').strip()
884                coords = list(map(float, line.split()))
885                if coords[4] == 1:
886                    check = True
887                    self.GCPcount += 1
888                else:
889                    check = False
890
891                self.AddGCP(event=None)
892                self.SetGCPData('source', (coords[0], coords[1]), sourceMapWin)
893                self.SetGCPData('target', (coords[2], coords[3]), targetMapWin)
894                index = self.list.GetSelected()
895                if index != wx.NOT_FOUND:
896                    self.list.CheckItem(index, check)
897                GCPcnt += 1
898
899        except IOError as err:
900            GError(
901                parent=self,
902                message="%s <%s>. %s%s" %
903                (_("Reading POINTS file failed"),
904                 self.file['points'],
905                    os.linesep,
906                    err))
907            return
908
909        f.close()
910
911        if GCPcnt == 0:
912            # 3 gcp is minimum
913            for i in range(3):
914                self.AddGCP(None)
915
916        if self.CheckGCPcount():
917            # calculate RMS
918            self.RMSError(self.xygroup, self.gr_order)
919
920    def ReloadGCPs(self, event):
921        """Reload data from file"""
922
923        # use backup
924        shutil.copy(self.file['points_bak'], self.file['points'])
925
926        # delete all items in mapcoordlist
927        self.mapcoordlist = []
928        self.mapcoordlist.append([0,        # GCP number
929                                  0.0,      # source east
930                                  0.0,      # source north
931                                  0.0,      # target east
932                                  0.0,      # target north
933                                  0.0,      # forward error
934                                  0.0])   # backward error
935
936        self.list.LoadData()
937        self.itemDataMap = self.mapcoordlist
938
939        if self._col != -1:
940            self.list.ClearColumnImage(self._col)
941        self._colSortFlag = [1] * self.list.GetColumnCount()
942
943        # draw GCPs (source and target)
944        sourceMapWin = self.SrcMapWindow
945        sourceMapWin.UpdateMap(render=False)
946        if self.show_target:
947            targetMapWin = self.TgtMapWindow
948            targetMapWin.UpdateMap(render=False)
949
950    def OnFocus(self, event):
951        # TODO: it is here just to remove old or obsolate beavior of base class gcp/MapFrame?
952        # self.grwiz.SwitchEnv('source')
953        pass
954
955    def _onMouseLeftUpPointer(self, mapWindow, x, y):
956        if mapWindow == self.SrcMapWindow:
957            coordtype = 'source'
958        else:
959            coordtype = 'target'
960
961        coord = (x, y)
962        self.SetGCPData(coordtype, coord, self, confirm=True)
963        mapWindow.UpdateMap(render=False)
964
965    def OnRMS(self, event):
966        """
967        RMS button handler
968        """
969        self.RMSError(self.xygroup, self.gr_order)
970
971        sourceMapWin = self.SrcMapWindow
972        sourceMapWin.UpdateMap(render=False)
973        if self.show_target:
974            targetMapWin = self.TgtMapWindow
975            targetMapWin.UpdateMap(render=False)
976
977    def CheckGCPcount(self, msg=False):
978        """
979        Checks to make sure that the minimum number of GCPs have been defined and
980        are active for the selected transformation order
981        """
982        if (self.GCPcount < 3 and self.gr_order == 1) or \
983                (self.GCPcount < 6 and self.gr_order == 2) or \
984                (self.GCPcount < 10 and self.gr_order == 3):
985            if msg:
986                GWarning(
987                    parent=self, message=_(
988                        'Insufficient points defined and active (checked) '
989                        'for selected rectification method (order: %d).\n'
990                        '3+ points needed for 1st order,\n'
991                        '6+ points for 2nd order, and\n'
992                        '10+ points for 3rd order.') %
993                    self.gr_order)
994                return False
995        else:
996            return True
997
998    def OnGeorect(self, event):
999        """
1000        Georectifies map(s) in group using i.rectify
1001        """
1002        global maptype
1003        self.SaveGCPs(None)
1004
1005        if self.CheckGCPcount(msg=True) == False:
1006            return
1007
1008        if maptype == 'raster':
1009            self.grwiz.SwitchEnv('source')
1010
1011            if self.clip_to_region:
1012                flags = "ac"
1013            else:
1014                flags = "a"
1015
1016            busy = wx.BusyInfo(_("Rectifying images, please wait..."),
1017                               parent=self)
1018            wx.GetApp().Yield()
1019
1020            ret, msg = RunCommand('i.rectify',
1021                                  parent=self,
1022                                  getErrorMsg=True,
1023                                  quiet=True,
1024                                  group=self.xygroup,
1025                                  extension=self.extension,
1026                                  order=self.gr_order,
1027                                  method=self.gr_method,
1028                                  flags=flags)
1029
1030            del busy
1031
1032            # provide feedback on failure
1033            if ret != 0:
1034                print('ip2i: Error in i.rectify', file=sys.stderr)
1035                print(self.grwiz.src_map, file=sys.stderr)
1036                print(msg, file=sys.stderr)
1037
1038            busy = wx.BusyInfo(_("Writing output image to group, please wait..."),
1039                               parent=self)
1040            wx.GetApp().Yield()
1041
1042            ret1, msg1 = RunCommand('i.group',
1043                                  parent=self,
1044                                  getErrorMsg=True,
1045                                  quiet=False,
1046                                  group=self.xygroup,
1047                                  input=''.join([self.grwiz.src_map.split('@')[0],self.extension]))
1048
1049            del busy
1050
1051            if ret1 != 0:
1052                print('ip2i: Error in i.group', file=sys.stderr)
1053                print(self.grwiz.src_map.split('@')[0], file=sys.stderr)
1054                print(self.extension, file=sys.stderr)
1055                print(msg1, file=sys.stderr)
1056
1057        self.grwiz.SwitchEnv('target')
1058
1059    def OnGeorectDone(self, **kargs):
1060        """Print final message"""
1061        global maptype
1062        if maptype == 'raster':
1063            return
1064
1065    def OnSettings(self, event):
1066        """GCP Manager settings"""
1067        dlg = GrSettingsDialog(parent=self, giface=self._giface,
1068                               id=wx.ID_ANY, title=_('GCP Manager settings'))
1069
1070        if dlg.ShowModal() == wx.ID_OK:
1071            pass
1072
1073        dlg.Destroy()
1074
1075    def UpdateColours(self, srcrender=False,
1076                      tgtrender=False):
1077        """update colours"""
1078        highest_fwd_err = 0.0
1079        self.highest_key = 0
1080        highest_idx = 0
1081
1082        for index in range(self.list.GetItemCount()):
1083            if self.list.IsItemChecked(index):
1084                key = self.list.GetItemData(index)
1085                fwd_err = self.mapcoordlist[key][5]
1086
1087                if self.highest_only == True:
1088                    self.list.SetItemTextColour(index, wx.BLACK)
1089                    if highest_fwd_err < fwd_err:
1090                        highest_fwd_err = fwd_err
1091                        self.highest_key = key
1092                        highest_idx = index
1093                elif self.rmsthresh > 0:
1094                    if (fwd_err > self.rmsthresh):
1095                        self.list.SetItemTextColour(index, wx.RED)
1096                    else:
1097                        self.list.SetItemTextColour(index, wx.BLACK)
1098            else:
1099                self.list.SetItemTextColour(index, wx.BLACK)
1100
1101        if self.highest_only and highest_fwd_err > 0.0:
1102            self.list.SetItemTextColour(highest_idx, wx.RED)
1103
1104        sourceMapWin = self.SrcMapWindow
1105        sourceMapWin.UpdateMap(render=srcrender)
1106        if self.show_target:
1107            targetMapWin = self.TgtMapWindow
1108            targetMapWin.UpdateMap(render=tgtrender)
1109
1110    def OnQuit(self, event):
1111        """Quit georectifier"""
1112        ret = wx.MessageBox(
1113            parent=self, caption=_("Quit GCP Manager"),
1114            message=_('Save ground control points?'),
1115            style=wx.ICON_QUESTION | wx.YES_NO | wx.CANCEL | wx.CENTRE)
1116
1117        if ret != wx.CANCEL:
1118            if ret == wx.YES:
1119                self.SaveGCPs(None)
1120            elif ret == wx.NO:
1121                # restore POINTS file from backup
1122                if os.path.exists(self.file['points_bak']):
1123                    shutil.copy(self.file['points_bak'], self.file['points'])
1124                    shutil.copy(self.file['points_bak'], self.file['ref_points'])
1125
1126            if os.path.exists(self.file['points_bak']):
1127                os.unlink(self.file['points_bak'])
1128
1129            self.SrcMap.Clean()
1130            self.TgtMap.Clean()
1131
1132            self.Destroy()
1133
1134        # event.Skip()
1135
1136    def OnGROrder(self, event):
1137        """
1138        sets transformation order for georectifying
1139        """
1140        if event:
1141            self.gr_order = event.GetInt() + 1
1142
1143        numOfItems = self.list.GetItemCount()
1144        minNumOfItems = numOfItems
1145
1146        if self.gr_order == 1:
1147            minNumOfItems = 3
1148            # self.SetStatusText(_('Insufficient points, 3+ points needed for 1st order'))
1149
1150        elif self.gr_order == 2:
1151            minNumOfItems = 6
1152            diff = 6 - numOfItems
1153            # self.SetStatusText(_('Insufficient points, 6+ points needed for 2nd order'))
1154
1155        elif self.gr_order == 3:
1156            minNumOfItems = 10
1157            # self.SetStatusText(_('Insufficient points, 10+ points needed for 3rd order'))
1158
1159        for i in range(minNumOfItems - numOfItems):
1160            self.AddGCP(None)
1161
1162        return minNumOfItems
1163
1164    def RMSError(self, xygroup, order):
1165        """
1166        Uses m.transform to calculate forward and backward error for each used GCP
1167        in POINTS file and insert error values into GCP list.
1168        Calculates total forward and backward RMS error for all used points
1169        """
1170        # save GCPs to points file to make sure that all checked GCPs are used
1171        self.SaveGCPs(None)
1172        # self.SetStatusText('')
1173
1174        if self.CheckGCPcount(msg=True) == False:
1175            return
1176
1177        # get list of forward and reverse rms error values for each point
1178        self.grwiz.SwitchEnv('source')
1179
1180        ret = RunCommand('m.transform',
1181                         parent=self,
1182                         read=True,
1183                         group=xygroup,
1184                         order=order)
1185
1186        self.grwiz.SwitchEnv('target')
1187
1188        if ret:
1189            errlist = ret.splitlines()
1190        else:
1191            GError(parent=self,
1192                   message=_('Could not calculate RMS Error.\n'
1193                             'Possible error with m.transform.'))
1194            return
1195
1196        # insert error values into GCP list for checked items
1197        sdfactor = float(
1198            UserSettings.Get(
1199                group='gcpman',
1200                key='rms',
1201                subkey='sdfactor'))
1202        GCPcount = 0
1203        sumsq_fwd_err = 0.0
1204        sumsq_bkw_err = 0.0
1205        sum_fwd_err = 0.0
1206        highest_fwd_err = 0.0
1207        self.highest_key = 0
1208        highest_idx = 0
1209
1210        for index in range(self.list.GetItemCount()):
1211            key = self.list.GetItemData(index)
1212            if self.list.IsItemChecked(index):
1213                fwd_err, bkw_err = errlist[GCPcount].split()
1214                self.list.SetItem(index, 5, fwd_err)
1215                self.list.SetItem(index, 6, bkw_err)
1216                self.mapcoordlist[key][5] = float(fwd_err)
1217                self.mapcoordlist[key][6] = float(bkw_err)
1218                self.list.SetItemTextColour(index, wx.BLACK)
1219                if self.highest_only:
1220                    if highest_fwd_err < float(fwd_err):
1221                        highest_fwd_err = float(fwd_err)
1222                        self.highest_key = key
1223                        highest_idx = index
1224
1225                sumsq_fwd_err += float(fwd_err)**2
1226                sumsq_bkw_err += float(bkw_err)**2
1227                sum_fwd_err += float(fwd_err)
1228                GCPcount += 1
1229            else:
1230                self.list.SetItem(index, 5, '')
1231                self.list.SetItem(index, 6, '')
1232                self.mapcoordlist[key][5] = 0.0
1233                self.mapcoordlist[key][6] = 0.0
1234                self.list.SetItemTextColour(index, wx.BLACK)
1235
1236        # SD
1237        if GCPcount > 0:
1238            self.rmsmean = sum_fwd_err / GCPcount
1239            self.rmssd = ((sumsq_fwd_err - self.rmsmean**2)**0.5)
1240            self.rmsthresh = self.rmsmean + sdfactor * self.rmssd
1241        else:
1242            self.rmsthresh = 0
1243            self.rmsmean = 0
1244            self.rmssd = 0
1245
1246        if self.highest_only and highest_fwd_err > 0.0:
1247            self.list.SetItemTextColour(highest_idx, wx.RED)
1248        elif GCPcount > 0 and self.rmsthresh > 0 and not self.highest_only:
1249            for index in range(self.list.GetItemCount()):
1250                if self.list.IsItemChecked(index):
1251                    key = self.list.GetItemData(index)
1252                    if (self.mapcoordlist[key][5] > self.rmsthresh):
1253                        self.list.SetItemTextColour(index, wx.RED)
1254
1255        # calculate global RMS error (geometric mean)
1256        self.fwd_rmserror = round((sumsq_fwd_err / GCPcount)**0.5, 4)
1257        self.bkw_rmserror = round((sumsq_bkw_err / GCPcount)**0.5, 4)
1258        self.list.ResizeColumns()
1259
1260    def GetNewExtent(self, region, map=None):
1261
1262        coord_file = utils.GetTempfile()
1263        newreg = {'n': 0.0, 's': 0.0, 'e': 0.0, 'w': 0.0, }
1264
1265        try:
1266            f = open(coord_file, mode='w')
1267            # NW corner
1268            f.write(str(region['e']) + " " + str(region['n']) + "\n")
1269            # NE corner
1270            f.write(str(region['e']) + " " + str(region['s']) + "\n")
1271            # SW corner
1272            f.write(str(region['w']) + " " + str(region['n']) + "\n")
1273            # SE corner
1274            f.write(str(region['w']) + " " + str(region['s']) + "\n")
1275        finally:
1276            f.close()
1277
1278        # save GCPs to points file to make sure that all checked GCPs are used
1279        self.SaveGCPs(None)
1280
1281        order = self.gr_order
1282        self.gr_order = 1
1283
1284        if self.CheckGCPcount(msg=True) == False:
1285            self.gr_order = order
1286            return
1287
1288        self.gr_order = order
1289
1290        # get list of forward and reverse rms error values for each point
1291        self.grwiz.SwitchEnv('source')
1292
1293        if map == 'source':
1294            ret = RunCommand('m.transform',
1295                             parent=self,
1296                             read=True,
1297                             group=self.xygroup,
1298                             order=1,
1299                             format='dst',
1300                             coords=coord_file)
1301
1302        elif map == 'target':
1303            ret = RunCommand('m.transform',
1304                             parent=self,
1305                             read=True,
1306                             group=self.xygroup,
1307                             order=1,
1308                             flags='r',
1309                             format='src',
1310                             coords=coord_file)
1311
1312        os.unlink(coord_file)
1313
1314        self.grwiz.SwitchEnv('target')
1315
1316        if ret:
1317            errlist = ret.splitlines()
1318        else:
1319            GError(parent=self,
1320                   message=_('Could not calculate new extends.\n'
1321                             'Possible error with m.transform.'))
1322            return
1323
1324        # fist corner
1325        e, n = errlist[0].split()
1326        fe = float(e)
1327        fn = float(n)
1328        newreg['n'] = fn
1329        newreg['s'] = fn
1330        newreg['e'] = fe
1331        newreg['w'] = fe
1332        # other three corners
1333        for i in range(1, 4):
1334            e, n = errlist[i].split()
1335            fe = float(e)
1336            fn = float(n)
1337            if fe < newreg['w']:
1338                newreg['w'] = fe
1339            if fe > newreg['e']:
1340                newreg['e'] = fe
1341            if fn < newreg['s']:
1342                newreg['s'] = fn
1343            if fn > newreg['n']:
1344                newreg['n'] = fn
1345
1346        return newreg
1347
1348    def OnHelp(self, event):
1349        """Show GCP Manager manual page"""
1350        self._giface.Help(entry='wxGUI.gcp')
1351
1352    def OnUpdateActive(self, event):
1353
1354        if self.activemap.GetSelection() == 0:
1355            self.MapWindow = self.SrcMapWindow
1356            self.Map = self.SrcMap
1357        else:
1358            self.MapWindow = self.TgtMapWindow
1359            self.Map = self.TgtMap
1360
1361        self.UpdateActive(self.MapWindow)
1362        # for wingrass
1363        if os.name == 'nt':
1364            self.MapWindow.SetFocus()
1365
1366    def UpdateActive(self, win):
1367
1368        # optionally disable tool zoomback tool
1369        self.GetMapToolbar().Enable('zoomback',
1370                                    enable=(len(self.MapWindow.zoomhistory) > 1))
1371
1372        if self.activemap.GetSelection() != (win == self.TgtMapWindow):
1373            self.activemap.SetSelection(win == self.TgtMapWindow)
1374        self.StatusbarUpdate()
1375
1376    def AdjustMap(self, newreg):
1377        """Adjust map window to new extents
1378        """
1379
1380        # adjust map window
1381        self.Map.region['n'] = newreg['n']
1382        self.Map.region['s'] = newreg['s']
1383        self.Map.region['e'] = newreg['e']
1384        self.Map.region['w'] = newreg['w']
1385
1386        self.MapWindow.ZoomHistory(self.Map.region['n'], self.Map.region['s'],
1387                                   self.Map.region['e'], self.Map.region['w'])
1388
1389        # LL locations
1390        if self.Map.projinfo['proj'] == 'll':
1391            if newreg['n'] > 90.0:
1392                newreg['n'] = 90.0
1393            if newreg['s'] < -90.0:
1394                newreg['s'] = -90.0
1395
1396        ce = newreg['w'] + (newreg['e'] - newreg['w']) / 2
1397        cn = newreg['s'] + (newreg['n'] - newreg['s']) / 2
1398
1399        # calculate new center point and display resolution
1400        self.Map.region['center_easting'] = ce
1401        self.Map.region['center_northing'] = cn
1402        self.Map.region["ewres"] = (newreg['e'] - newreg['w']) / self.Map.width
1403        self.Map.region["nsres"] = (
1404            newreg['n'] - newreg['s']) / self.Map.height
1405        self.Map.AlignExtentFromDisplay()
1406
1407        self.MapWindow.ZoomHistory(self.Map.region['n'], self.Map.region['s'],
1408                                   self.Map.region['e'], self.Map.region['w'])
1409
1410        if self.MapWindow.redrawAll is False:
1411            self.MapWindow.redrawAll = True
1412
1413        self.MapWindow.UpdateMap()
1414        self.StatusbarUpdate()
1415
1416    def OnZoomToSource(self, event):
1417        """Set target map window to match extents of source map window
1418        """
1419
1420        if not self.MapWindow == self.TgtMapWindow:
1421            self.MapWindow = self.TgtMapWindow
1422            self.Map = self.TgtMap
1423            self.UpdateActive(self.TgtMapWindow)
1424
1425        # get new N, S, E, W for target
1426        newreg = self.GetNewExtent(self.SrcMap.region, 'source')
1427        if newreg:
1428            self.AdjustMap(newreg)
1429
1430    def OnZoomToTarget(self, event):
1431        """Set source map window to match extents of target map window
1432        """
1433
1434        if not self.MapWindow == self.SrcMapWindow:
1435            self.MapWindow = self.SrcMapWindow
1436            self.Map = self.SrcMap
1437            self.UpdateActive(self.SrcMapWindow)
1438
1439        # get new N, S, E, W for target
1440        newreg = self.GetNewExtent(self.TgtMap.region, 'target')
1441        if newreg:
1442            self.AdjustMap(newreg)
1443
1444    def OnZoomMenuGCP(self, event):
1445        """Popup Zoom menu
1446        """
1447        point = wx.GetMousePosition()
1448        zoommenu = Menu()
1449        # Add items to the menu
1450
1451        zoomsource = wx.MenuItem(zoommenu, wx.ID_ANY, _(
1452            'Adjust source display to target display'))
1453        zoommenu.AppendItem(zoomsource)
1454        self.Bind(wx.EVT_MENU, self.OnZoomToTarget, zoomsource)
1455
1456        zoomtarget = wx.MenuItem(zoommenu, wx.ID_ANY, _(
1457            'Adjust target display to source display'))
1458        zoommenu.AppendItem(zoomtarget)
1459        self.Bind(wx.EVT_MENU, self.OnZoomToSource, zoomtarget)
1460
1461        # Popup the menu. If an item is selected then its handler
1462        # will be called before PopupMenu returns.
1463        self.PopupMenu(zoommenu)
1464        zoommenu.Destroy()
1465
1466    def OnSize(self, event):
1467        """Adjust Map Windows after GCP Map Display has been resized
1468        """
1469        # re-render image on idle
1470        self.resize = grass.clock()
1471        super(MapFrame, self).OnSize(event)
1472
1473    def OnIdle(self, event):
1474        """GCP Map Display resized, adjust Map Windows
1475        """
1476        if self.GetMapToolbar():
1477            if self.resize and self.resize + 0.2 < grass.clock():
1478                srcwidth, srcheight = self.SrcMapWindow.GetSize()
1479                tgtwidth, tgtheight = self.TgtMapWindow.GetSize()
1480                srcwidth = (srcwidth + tgtwidth) / 2
1481                if self.show_target:
1482                    self._mgr.GetPane("target").Hide()
1483                    self._mgr.Update()
1484                self._mgr.GetPane("source").BestSize((srcwidth, srcheight))
1485                self._mgr.GetPane("target").BestSize((srcwidth, tgtheight))
1486                if self.show_target:
1487                    self._mgr.GetPane("target").Show()
1488                self._mgr.Update()
1489                self.resize = False
1490            elif self.resize:
1491                event.RequestMore()
1492        pass
1493
1494
1495class GCPList(ListCtrl,
1496              CheckListCtrlMixin,
1497              ListCtrlAutoWidthMixin):
1498
1499    def __init__(self, parent, gcp, id=wx.ID_ANY,
1500                 pos=wx.DefaultPosition, size=wx.DefaultSize,
1501                 style=wx.LC_REPORT | wx.SUNKEN_BORDER | wx.LC_HRULES |
1502                 wx.LC_SINGLE_SEL):
1503
1504        ListCtrl.__init__(self, parent, id, pos, size, style)
1505
1506        self.gcp = gcp  # GCP class
1507        self.render = True
1508
1509        # Mixin settings
1510        CheckListCtrlMixin.__init__(self)
1511        ListCtrlAutoWidthMixin.__init__(self)
1512        # TextEditMixin.__init__(self)
1513
1514        # tracks whether list items are checked or not
1515        self.CheckList = []
1516
1517        self._Create()
1518
1519        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected)
1520        self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated)
1521        self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick)
1522
1523        self.selected = wx.NOT_FOUND
1524        self.selectedkey = -1
1525
1526    def _Create(self):
1527
1528        if 0:
1529            # normal, simple columns
1530            idx_col = 0
1531            for col in (_('use'),
1532                        _('source X'),
1533                        _('source Y'),
1534                        _('target X'),
1535                        _('target Y'),
1536                        _('Forward error'),
1537                        _('Backward error')):
1538                self.InsertColumn(idx_col, col)
1539                idx_col += 1
1540        else:
1541            # the hard way: we want images on the column header
1542            info = wx.ListItem()
1543            info.SetMask(
1544                wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT)
1545            info.SetImage(-1)
1546            info.m_format = wx.LIST_FORMAT_LEFT
1547
1548            idx_col = 0
1549            for lbl in (_('use'),
1550                        _('source X'),
1551                        _('source Y'),
1552                        _('target X'),
1553                        _('target Y'),
1554                        _('Forward error'),
1555                        _('Backward error')):
1556                info.SetText(lbl)
1557                self.InsertColumn(idx_col, info)
1558                idx_col += 1
1559
1560    def LoadData(self):
1561        """Load data into list"""
1562        self.DeleteAllItems()
1563
1564        self.render = False
1565        if os.path.isfile(self.gcp.file['points']):
1566            self.gcp.ReadGCPs()
1567        else:
1568            # 3 gcp is minimum
1569            for i in range(3):
1570                self.gcp.AddGCP(None)
1571
1572        # select first point by default
1573        self.selected = 0
1574        self.selectedkey = self.GetItemData(self.selected)
1575        self.SetItemState(self.selected,
1576                          wx.LIST_STATE_SELECTED,
1577                          wx.LIST_STATE_SELECTED)
1578
1579        self.ResizeColumns()
1580        self.render = True
1581
1582        self.EnsureVisible(self.selected)
1583
1584    def OnCheckItem(self, index, flag):
1585        """Item is checked/unchecked"""
1586
1587        if self.render:
1588            # redraw points
1589            sourceMapWin = self.gcp.SrcMapWindow
1590            sourceMapWin.UpdateMap(render=False)
1591            if self.gcp.show_target:
1592                targetMapWin = self.gcp.TgtMapWindow
1593                targetMapWin.UpdateMap(render=False)
1594
1595    def AddGCPItem(self):
1596        """
1597        Appends an item to GCP list
1598        """
1599        self.selectedkey = self.GetItemCount() + 1
1600
1601        self.Append([str(self.selectedkey),    # GCP number
1602                     '0.0',                # source E
1603                     '0.0',                # source N
1604                     '0.0',                # target E
1605                     '0.0',                # target N
1606                     '',                   # forward error
1607                     ''])                  # backward error
1608
1609        self.selected = self.GetItemCount() - 1
1610        self.SetItemData(self.selected, self.selectedkey)
1611
1612        self.SetItemState(self.selected,
1613                          wx.LIST_STATE_SELECTED,
1614                          wx.LIST_STATE_SELECTED)
1615
1616        self.ResizeColumns()
1617
1618        self.gcp.pointsToDrawSrc.AddItem(
1619            coords=[0, 0], label=str(self.selectedkey))
1620        self.gcp.pointsToDrawTgt.AddItem(
1621            coords=[0, 0], label=str(self.selectedkey))
1622
1623        self.EnsureVisible(self.selected)
1624
1625        return self.selected
1626
1627    def DeleteGCPItem(self):
1628        """Deletes selected item in GCP list.
1629        """
1630        if self.selected == wx.NOT_FOUND:
1631            return
1632
1633        key = self.GetItemData(self.selected)
1634        self.DeleteItem(self.selected)
1635
1636        if self.selected != wx.NOT_FOUND:
1637            item = self.gcp.pointsToDrawSrc.GetItem(key - 1)
1638            self.gcp.pointsToDrawSrc.DeleteItem(item)
1639
1640            item = self.gcp.pointsToDrawTgt.GetItem(key - 1)
1641            self.gcp.pointsToDrawTgt.DeleteItem(item)
1642
1643        return key
1644
1645    def ResizeColumns(self):
1646        """Resize columns"""
1647        minWidth = [90, 120]
1648        for i in range(self.GetColumnCount()):
1649            self.SetColumnWidth(i, wx.LIST_AUTOSIZE)
1650            # first column is checkbox, don't set to minWidth
1651            if i > 0 and self.GetColumnWidth(i) < minWidth[i > 4]:
1652                self.SetColumnWidth(i, minWidth[i > 4])
1653
1654        self.SendSizeEvent()
1655
1656    def GetSelected(self):
1657        """Get index of selected item"""
1658        return self.selected
1659
1660    def OnItemSelected(self, event):
1661        """Item selected
1662        """
1663        if self.render and self.selected != event.GetIndex():
1664            self.selected = event.GetIndex()
1665            self.selectedkey = self.GetItemData(self.selected)
1666            sourceMapWin = self.gcp.SrcMapWindow
1667            sourceMapWin.UpdateMap(render=False)
1668            if self.gcp.show_target:
1669                targetMapWin = self.gcp.TgtMapWindow
1670                targetMapWin.UpdateMap(render=False)
1671        event.Skip()
1672
1673    def OnItemActivated(self, event):
1674        """
1675        When item double clicked, open editor to update coordinate values
1676        """
1677        coords = []
1678        index = event.GetIndex()
1679        key = self.GetItemData(index)
1680        changed = False
1681
1682        for i in range(1, 5):
1683            coords.append(self.GetItem(index, i).GetText())
1684
1685        dlg = EditGCP(parent=self, id=wx.ID_ANY, data=coords, gcpno=key)
1686
1687        if dlg.ShowModal() == wx.ID_OK:
1688            values = dlg.GetValues()  # string
1689
1690            if len(values) == 0:
1691                GError(parent=self, message=_(
1692                    "Invalid coordinate value. Operation canceled."))
1693            else:
1694                for i in range(len(values)):
1695                    if values[i] != coords[i]:
1696                        self.SetItem(index, i + 1, values[i])
1697                        changed = True
1698
1699                if changed:
1700                    # reset RMS and update mapcoordlist
1701                    self.SetItem(index, 5, '')
1702                    self.SetItem(index, 6, '')
1703                    key = self.GetItemData(index)
1704                    self.gcp.mapcoordlist[key] = [key,
1705                                                  float(values[0]),
1706                                                  float(values[1]),
1707                                                  float(values[2]),
1708                                                  float(values[3]),
1709                                                  0.0,
1710                                                  0.0]
1711
1712                    self.gcp.pointsToDrawSrc.GetItem(
1713                        key - 1).SetCoords([float(values[0]), float(values[1])])
1714                    self.gcp.pointsToDrawTgt.GetItem(
1715                        key - 1).SetCoords([float(values[2]), float(values[3])])
1716                    self.gcp.UpdateColours()
1717
1718    def OnColClick(self, event):
1719        """ListCtrl forgets selected item..."""
1720        self.selected = self.FindItem(-1, self.selectedkey)
1721        self.SetItemState(self.selected,
1722                          wx.LIST_STATE_SELECTED,
1723                          wx.LIST_STATE_SELECTED)
1724
1725        event.Skip()
1726
1727class EditGCP(wx.Dialog):
1728
1729    def __init__(self, parent, data, gcpno, id=wx.ID_ANY,
1730                 title=_("Edit GCP"),
1731                 style=wx.DEFAULT_DIALOG_STYLE):
1732        """Dialog for editing GPC and map coordinates in list control"""
1733
1734        wx.Dialog.__init__(self, parent, id, title=title, style=style)
1735
1736        panel = wx.Panel(parent=self)
1737
1738        sizer = wx.BoxSizer(wx.VERTICAL)
1739
1740        box = StaticBox(
1741            parent=panel, id=wx.ID_ANY, label=" %s %s " %
1742            (_("Ground Control Point No."), str(gcpno)))
1743        boxSizer = wx.StaticBoxSizer(box, wx.VERTICAL)
1744
1745        # source coordinates
1746        gridSizer = wx.GridBagSizer(vgap=5, hgap=5)
1747
1748        self.xcoord = TextCtrl(parent=panel, id=wx.ID_ANY, size=(150, -1))
1749        self.ycoord = TextCtrl(parent=panel, id=wx.ID_ANY, size=(150, -1))
1750        self.ecoord = TextCtrl(parent=panel, id=wx.ID_ANY, size=(150, -1))
1751        self.ncoord = TextCtrl(parent=panel, id=wx.ID_ANY, size=(150, -1))
1752
1753        # swap source N, target E
1754        tmp_coord = data[1]
1755        data[1] = data[2]
1756        data[2] = tmp_coord
1757
1758        row = 0
1759        col = 0
1760        idx = 0
1761        for label, win in ((_("source X:"), self.xcoord),
1762                           (_("target X:"), self.ecoord),
1763                           (_("source Y:"), self.ycoord),
1764                           (_("target Y:"), self.ncoord)):
1765            label = StaticText(parent=panel, id=wx.ID_ANY,
1766                               label=label)
1767            gridSizer.Add(label,
1768                          flag=wx.ALIGN_CENTER_VERTICAL,
1769                          pos=(row, col))
1770
1771            col += 1
1772            win.SetValue(str(data[idx]))
1773
1774            gridSizer.Add(win,
1775                          pos=(row, col))
1776
1777            col += 1
1778            idx += 1
1779
1780            if col > 3:
1781                row += 1
1782                col = 0
1783
1784        boxSizer.Add(gridSizer, proportion=1,
1785                     flag=wx.EXPAND | wx.ALL, border=5)
1786
1787        sizer.Add(boxSizer, proportion=1,
1788                  flag=wx.EXPAND | wx.ALL, border=5)
1789
1790        #
1791        # buttons
1792        #
1793        self.btnCancel = Button(panel, wx.ID_CANCEL)
1794        self.btnOk = Button(panel, wx.ID_OK)
1795        self.btnOk.SetDefault()
1796
1797        btnSizer = wx.StdDialogButtonSizer()
1798        btnSizer.AddButton(self.btnCancel)
1799        btnSizer.AddButton(self.btnOk)
1800        btnSizer.Realize()
1801
1802        sizer.Add(btnSizer, proportion=0,
1803                  flag=wx.ALIGN_RIGHT | wx.ALL, border=5)
1804
1805        panel.SetSizer(sizer)
1806        sizer.Fit(self)
1807
1808    def GetValues(self, columns=None):
1809        """Return list of values (as strings).
1810        """
1811        valuelist = []
1812        try:
1813            float(self.xcoord.GetValue())
1814            float(self.ycoord.GetValue())
1815            float(self.ecoord.GetValue())
1816            float(self.ncoord.GetValue())
1817        except ValueError:
1818            return valuelist
1819
1820        valuelist.append(self.xcoord.GetValue())
1821        valuelist.append(self.ycoord.GetValue())
1822        valuelist.append(self.ecoord.GetValue())
1823        valuelist.append(self.ncoord.GetValue())
1824
1825        return valuelist
1826
1827
1828class GrSettingsDialog(wx.Dialog):
1829
1830    def __init__(
1831            self, parent, id, giface, title, pos=wx.DefaultPosition,
1832            size=wx.DefaultSize, style=wx.DEFAULT_DIALOG_STYLE):
1833        wx.Dialog.__init__(self, parent, id, title, pos, size, style)
1834        """
1835        Dialog to set profile text options: font, title
1836        and font size, axis labels and font size
1837        """
1838        #
1839        # initialize variables
1840        #
1841        self.parent = parent
1842        self.new_src_map = src_map
1843        self.new_tgt_map = {'raster': tgt_map['raster']}
1844        self.sdfactor = 0
1845
1846        self.symbol = {}
1847
1848        self.methods = ["nearest",
1849                        "linear",
1850                        "linear_f",
1851                        "cubic",
1852                        "cubic_f",
1853                        "lanczos",
1854                        "lanczos_f"]
1855
1856        # notebook
1857        notebook = wx.Notebook(parent=self, id=wx.ID_ANY, style=wx.BK_DEFAULT)
1858        self.__CreateSymbologyPage(notebook)
1859        self.__CreateRectificationPage(notebook)
1860
1861        # buttons
1862        btnSave = Button(self, wx.ID_SAVE)
1863        btnApply = Button(self, wx.ID_APPLY)
1864        btnClose = Button(self, wx.ID_CLOSE)
1865        btnApply.SetDefault()
1866
1867        # bindings
1868        btnApply.Bind(wx.EVT_BUTTON, self.OnApply)
1869        btnApply.SetToolTip(_("Apply changes for the current session"))
1870        btnSave.Bind(wx.EVT_BUTTON, self.OnSave)
1871        btnSave.SetToolTip(
1872            _("Apply and save changes to user settings file (default for next sessions)"))
1873        btnClose.Bind(wx.EVT_BUTTON, self.OnClose)
1874        btnClose.SetToolTip(_("Close dialog"))
1875
1876        # sizers
1877        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
1878        btnSizer.Add(btnApply, flag=wx.LEFT | wx.RIGHT, border=5)
1879        btnSizer.Add(btnSave, flag=wx.LEFT | wx.RIGHT, border=5)
1880        btnSizer.Add(btnClose, flag=wx.LEFT | wx.RIGHT, border=5)
1881
1882        # sizers
1883        mainSizer = wx.BoxSizer(wx.VERTICAL)
1884        mainSizer.Add(
1885            notebook,
1886            proportion=1,
1887            flag=wx.EXPAND | wx.ALL,
1888            border=5)
1889        mainSizer.Add(btnSizer, proportion=0,
1890                      flag=wx.ALIGN_RIGHT | wx.ALL, border=5)
1891        #              flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5)
1892
1893        self.SetSizer(mainSizer)
1894        mainSizer.Fit(self)
1895
1896    def __CreateSymbologyPage(self, notebook):
1897        """Create notebook page with symbology settings"""
1898
1899        panel = wx.Panel(parent=notebook, id=wx.ID_ANY)
1900        notebook.AddPage(page=panel, text=_("Symbology"))
1901
1902        sizer = wx.BoxSizer(wx.VERTICAL)
1903
1904        rmsgridSizer = wx.GridBagSizer(vgap=5, hgap=5)
1905
1906        # highlight only highest forward RMS error
1907        self.highlighthighest = wx.CheckBox(
1908            parent=panel, id=wx.ID_ANY,
1909            label=_("Highlight highest RMS error only"))
1910        hh = UserSettings.Get(group='gcpman', key='rms', subkey='highestonly')
1911        self.highlighthighest.SetValue(hh)
1912        rmsgridSizer.Add(
1913            self.highlighthighest,
1914            flag=wx.ALIGN_CENTER_VERTICAL,
1915            pos=(
1916                0,
1917                0))
1918
1919        # RMS forward error threshold
1920        rmslabel = StaticText(
1921            parent=panel, id=wx.ID_ANY,
1922            label=_("Highlight RMS error > M + SD * factor:"))
1923        rmslabel.SetToolTip(
1924            wx.ToolTip(
1925                _(
1926                    "Highlight GCPs with an RMS error larger than \n"
1927                    "mean + standard deviation * given factor. \n"
1928                    "Recommended values for this factor are between 1 and 2.")))
1929        rmsgridSizer.Add(
1930            rmslabel,
1931            flag=wx.ALIGN_CENTER_VERTICAL,
1932            pos=(
1933                1,
1934                0))
1935        sdfactor = UserSettings.Get(
1936            group='gcpman', key='rms', subkey='sdfactor')
1937        self.rmsWin = TextCtrl(parent=panel, id=wx.ID_ANY,
1938                               size=(70, -1), style=wx.TE_NOHIDESEL)
1939        self.rmsWin.SetValue("%s" % str(sdfactor))
1940        if (self.parent.highest_only == True):
1941            self.rmsWin.Disable()
1942
1943        self.symbol['sdfactor'] = self.rmsWin.GetId()
1944        rmsgridSizer.Add(self.rmsWin, flag=wx.ALIGN_RIGHT, pos=(1, 1))
1945        rmsgridSizer.AddGrowableCol(1)
1946        sizer.Add(rmsgridSizer, flag=wx.EXPAND | wx.ALL, border=5)
1947
1948        box = StaticBox(parent=panel, id=wx.ID_ANY,
1949                        label=" %s " % _("Symbol settings"))
1950        boxSizer = wx.StaticBoxSizer(box, wx.VERTICAL)
1951        gridSizer = wx.GridBagSizer(vgap=5, hgap=5)
1952
1953        #
1954        # general symbol color
1955        #
1956        row = 0
1957        label = StaticText(parent=panel, id=wx.ID_ANY, label=_("Color:"))
1958        gridSizer.Add(label, flag=wx.ALIGN_CENTER_VERTICAL, pos=(row, 0))
1959        col = UserSettings.Get(group='gcpman', key='symbol', subkey='color')
1960        colWin = csel.ColourSelect(parent=panel, id=wx.ID_ANY,
1961                                   colour=wx.Colour(col[0],
1962                                                    col[1],
1963                                                    col[2],
1964                                                    255))
1965        self.symbol['color'] = colWin.GetId()
1966        gridSizer.Add(colWin,
1967                      flag=wx.ALIGN_RIGHT,
1968                      pos=(row, 1))
1969
1970        #
1971        # symbol color for high forward RMS error
1972        #
1973        row += 1
1974        label = StaticText(
1975            parent=panel,
1976            id=wx.ID_ANY,
1977            label=_("Color for high RMS error:"))
1978        gridSizer.Add(label, flag=wx.ALIGN_CENTER_VERTICAL, pos=(row, 0))
1979        hcol = UserSettings.Get(group='gcpman', key='symbol', subkey='hcolor')
1980        hcolWin = csel.ColourSelect(parent=panel, id=wx.ID_ANY,
1981                                    colour=wx.Colour(hcol[0],
1982                                                     hcol[1],
1983                                                     hcol[2],
1984                                                     255))
1985        self.symbol['hcolor'] = hcolWin.GetId()
1986        gridSizer.Add(hcolWin,
1987                      flag=wx.ALIGN_RIGHT,
1988                      pos=(row, 1))
1989
1990        #
1991        # symbol color for selected GCP
1992        #
1993        row += 1
1994        label = StaticText(
1995            parent=panel,
1996            id=wx.ID_ANY,
1997            label=_("Color for selected GCP:"))
1998        gridSizer.Add(label, flag=wx.ALIGN_CENTER_VERTICAL, pos=(row, 0))
1999        scol = UserSettings.Get(group='gcpman', key='symbol', subkey='scolor')
2000        scolWin = csel.ColourSelect(parent=panel, id=wx.ID_ANY,
2001                                    colour=wx.Colour(scol[0],
2002                                                     scol[1],
2003                                                     scol[2],
2004                                                     255))
2005        self.symbol['scolor'] = scolWin.GetId()
2006        gridSizer.Add(scolWin,
2007                      flag=wx.ALIGN_RIGHT,
2008                      pos=(row, 1))
2009
2010        #
2011        # symbol color for unused GCP
2012        #
2013        row += 1
2014        label = StaticText(
2015            parent=panel,
2016            id=wx.ID_ANY,
2017            label=_("Color for unused GCPs:"))
2018        gridSizer.Add(label, flag=wx.ALIGN_CENTER_VERTICAL, pos=(row, 0))
2019        ucol = UserSettings.Get(group='gcpman', key='symbol', subkey='ucolor')
2020        ucolWin = csel.ColourSelect(parent=panel, id=wx.ID_ANY,
2021                                    colour=wx.Colour(ucol[0],
2022                                                     ucol[1],
2023                                                     ucol[2],
2024                                                     255))
2025        self.symbol['ucolor'] = ucolWin.GetId()
2026        gridSizer.Add(ucolWin,
2027                      flag=wx.ALIGN_RIGHT,
2028                      pos=(row, 1))
2029
2030        # show unused GCPs
2031        row += 1
2032        self.showunused = wx.CheckBox(parent=panel, id=wx.ID_ANY,
2033                                      label=_("Show unused GCPs"))
2034        shuu = UserSettings.Get(group='gcpman', key='symbol', subkey='unused')
2035        self.showunused.SetValue(shuu)
2036        gridSizer.Add(
2037            self.showunused,
2038            flag=wx.ALIGN_CENTER_VERTICAL,
2039            pos=(
2040                row,
2041                0))
2042
2043        #
2044        # symbol size
2045        #
2046        row += 1
2047        label = StaticText(
2048            parent=panel,
2049            id=wx.ID_ANY,
2050            label=_("Symbol size:"))
2051        gridSizer.Add(label, flag=wx.ALIGN_CENTER_VERTICAL, pos=(row, 0))
2052        symsize = int(
2053            UserSettings.Get(
2054                group='gcpman',
2055                key='symbol',
2056                subkey='size'))
2057        sizeWin = SpinCtrl(parent=panel, id=wx.ID_ANY,
2058                           min=1, max=20)
2059        sizeWin.SetValue(symsize)
2060        self.symbol['size'] = sizeWin.GetId()
2061        gridSizer.Add(sizeWin,
2062                      flag=wx.ALIGN_RIGHT,
2063                      pos=(row, 1))
2064
2065        #
2066        # symbol width
2067        #
2068        row += 1
2069        label = StaticText(
2070            parent=panel,
2071            id=wx.ID_ANY,
2072            label=_("Line width:"))
2073        gridSizer.Add(label, flag=wx.ALIGN_CENTER_VERTICAL, pos=(row, 0))
2074        width = int(
2075            UserSettings.Get(
2076                group='gcpman',
2077                key='symbol',
2078                subkey='width'))
2079        widWin = SpinCtrl(parent=panel, id=wx.ID_ANY,
2080                          min=1, max=10)
2081        widWin.SetValue(width)
2082        self.symbol['width'] = widWin.GetId()
2083        gridSizer.Add(widWin,
2084                      flag=wx.ALIGN_RIGHT,
2085                      pos=(row, 1))
2086        gridSizer.AddGrowableCol(1)
2087
2088        boxSizer.Add(gridSizer, flag=wx.EXPAND)
2089        sizer.Add(boxSizer, flag=wx.EXPAND | wx.ALL, border=5)
2090
2091        #
2092        # maps to display
2093        #
2094        # source map to display
2095        self.srcselection = Select(
2096            panel,
2097            id=wx.ID_ANY,
2098            size=globalvar.DIALOG_GSELECT_SIZE,
2099            type='maptype',
2100            updateOnPopup=False)
2101        self.parent.grwiz.SwitchEnv('source')
2102        self.srcselection.SetElementList(maptype)
2103        # filter out all maps not in group
2104        self.srcselection.tcp.GetElementList(elements=self.parent.src_maps)
2105
2106        # target map(s) to display
2107        self.parent.grwiz.SwitchEnv('target')
2108        self.tgtrastselection = Select(
2109            panel, id=wx.ID_ANY, size=globalvar.DIALOG_GSELECT_SIZE,
2110            type='raster', updateOnPopup=False)
2111        self.tgtrastselection.SetElementList('cell')
2112        self.tgtrastselection.GetElementList()
2113
2114        sizer.Add(
2115            StaticText(
2116                parent=panel,
2117                id=wx.ID_ANY,
2118                label=_('Select source map to display:')),
2119            proportion=0,
2120            flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL,
2121            border=5)
2122        sizer.Add(
2123            self.srcselection,
2124            proportion=0,
2125            flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL,
2126            border=5)
2127        self.srcselection.SetValue(src_map)
2128        sizer.Add(
2129            StaticText(
2130                parent=panel,
2131                id=wx.ID_ANY,
2132                label=_('Select target raster map to display:')),
2133            proportion=0,
2134            flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL,
2135            border=5)
2136        sizer.Add(
2137            self.tgtrastselection,
2138            proportion=0,
2139            flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL,
2140            border=5)
2141        self.tgtrastselection.SetValue(tgt_map['raster'])
2142
2143        # bindings
2144        self.highlighthighest.Bind(wx.EVT_CHECKBOX, self.OnHighlight)
2145        self.rmsWin.Bind(wx.EVT_TEXT, self.OnSDFactor)
2146        self.srcselection.Bind(wx.EVT_TEXT, self.OnSrcSelection)
2147        self.tgtrastselection.Bind(wx.EVT_TEXT, self.OnTgtRastSelection)
2148
2149        panel.SetSizer(sizer)
2150
2151        return panel
2152
2153    def __CreateRectificationPage(self, notebook):
2154        """Create notebook page with symbology settings"""
2155
2156        panel = wx.Panel(parent=notebook, id=wx.ID_ANY)
2157        notebook.AddPage(page=panel, text=_("Rectification"))
2158
2159        sizer = wx.BoxSizer(wx.VERTICAL)
2160
2161        # transformation order
2162        self.rb_grorder = wx.RadioBox(
2163            parent=panel,
2164            id=wx.ID_ANY,
2165            label=" %s " %
2166            _("Select rectification order"),
2167            choices=[
2168                _('1st order'),
2169                _('2nd order'),
2170                _('3rd order')],
2171            majorDimension=wx.RA_SPECIFY_COLS)
2172        sizer.Add(self.rb_grorder, proportion=0,
2173                  flag=wx.EXPAND | wx.ALL, border=5)
2174        self.rb_grorder.SetSelection(self.parent.gr_order - 1)
2175
2176        # interpolation method
2177        gridSizer = wx.GridBagSizer(vgap=5, hgap=5)
2178        gridSizer.Add(
2179            StaticText(
2180                parent=panel,
2181                id=wx.ID_ANY,
2182                label=_('Select interpolation method:')),
2183            pos=(
2184                0,
2185                0),
2186            flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL,
2187            border=5)
2188        self.grmethod = wx.Choice(parent=panel, id=wx.ID_ANY,
2189                                  choices=self.methods)
2190        gridSizer.Add(self.grmethod, pos=(0, 1),
2191                      flag=wx.ALIGN_RIGHT, border=5)
2192        self.grmethod.SetStringSelection(self.parent.gr_method)
2193        gridSizer.AddGrowableCol(1)
2194        sizer.Add(gridSizer, flag=wx.EXPAND | wx.ALL, border=5)
2195
2196        # clip to region
2197        self.check = wx.CheckBox(parent=panel, id=wx.ID_ANY, label=_(
2198            "clip to computational region in target location"))
2199        sizer.Add(self.check, proportion=0,
2200                  flag=wx.EXPAND | wx.ALL, border=5)
2201        self.check.SetValue(self.parent.clip_to_region)
2202
2203        # extension
2204        sizer.Add(
2205            StaticText(
2206                parent=panel,
2207                id=wx.ID_ANY,
2208                label=_('Extension for output maps:')),
2209            proportion=0,
2210            flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL,
2211            border=5)
2212        self.ext_txt = TextCtrl(
2213            parent=panel, id=wx.ID_ANY, value="", size=(
2214                350, -1))
2215        self.ext_txt.SetValue(self.parent.extension)
2216        sizer.Add(
2217            self.ext_txt,
2218            proportion=0,
2219            flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL,
2220            border=5)
2221
2222        # bindings
2223        self.ext_txt.Bind(wx.EVT_TEXT, self.OnExtension)
2224        self.Bind(wx.EVT_RADIOBOX, self.parent.OnGROrder, self.rb_grorder)
2225        self.Bind(wx.EVT_CHOICE, self.OnMethod, self.grmethod)
2226        self.Bind(wx.EVT_CHECKBOX, self.OnClipRegion, self.check)
2227
2228        panel.SetSizer(sizer)
2229
2230        return panel
2231
2232    def OnHighlight(self, event):
2233        """Checkbox 'highlighthighest' checked/unchecked"""
2234        if self.highlighthighest.IsChecked():
2235            self.parent.highest_only = True
2236            self.rmsWin.Disable()
2237        else:
2238            self.parent.highest_only = False
2239            self.rmsWin.Enable()
2240
2241    def OnSDFactor(self, event):
2242        """New factor for RMS threshold = M + SD * factor"""
2243        try:
2244            self.sdfactor = float(self.rmsWin.GetValue())
2245        except ValueError:
2246            return
2247        if self.sdfactor <= 0:
2248            GError(parent=self,
2249                   message=_('RMS threshold factor must be > 0'))
2250        elif self.sdfactor < 1:
2251            GError(parent=self,
2252                   message=_('RMS threshold factor is < 1\n'
2253                             'Too many points might be highlighted'))
2254
2255    def OnSrcSelection(self, event):
2256        """Source map to display selected"""
2257        global src_map
2258
2259        tmp_map = self.srcselection.GetValue()
2260
2261        if not tmp_map == '' and not tmp_map == src_map:
2262            self.new_src_map = tmp_map
2263
2264    def OnTgtRastSelection(self, event):
2265        """Target map to display selected"""
2266        global tgt_map
2267
2268        self.new_tgt_map['raster'] = self.tgtrastselection.GetValue()
2269
2270    def OnMethod(self, event):
2271        self.parent.gr_method = self.methods[event.GetSelection()]
2272
2273    def OnClipRegion(self, event):
2274        self.parent.clip_to_region = event.IsChecked()
2275
2276    def OnExtension(self, event):
2277        self.parent.extension = self.ext_txt.GetValue()
2278
2279    def UpdateSettings(self):
2280        global src_map
2281        global tgt_map
2282        global maptype
2283
2284        layers = None
2285
2286        UserSettings.Set(group='gcpman', key='rms', subkey='highestonly',
2287                         value=self.highlighthighest.GetValue())
2288
2289        if self.sdfactor > 0:
2290            UserSettings.Set(group='gcpman', key='rms', subkey='sdfactor',
2291                             value=self.sdfactor)
2292
2293            self.parent.sdfactor = self.sdfactor
2294            if self.parent.rmsthresh > 0:
2295                self.parent.rmsthresh = self.parent.rmsmean + self.parent.sdfactor * self.parent.rmssd
2296
2297        UserSettings.Set(
2298            group='gcpman',
2299            key='symbol',
2300            subkey='color',
2301            value=tuple(
2302                wx.FindWindowById(
2303                    self.symbol['color']).GetColour()))
2304        UserSettings.Set(
2305            group='gcpman',
2306            key='symbol',
2307            subkey='hcolor',
2308            value=tuple(
2309                wx.FindWindowById(
2310                    self.symbol['hcolor']).GetColour()))
2311        UserSettings.Set(
2312            group='gcpman',
2313            key='symbol',
2314            subkey='scolor',
2315            value=tuple(
2316                wx.FindWindowById(
2317                    self.symbol['scolor']).GetColour()))
2318        UserSettings.Set(
2319            group='gcpman',
2320            key='symbol',
2321            subkey='ucolor',
2322            value=tuple(
2323                wx.FindWindowById(
2324                    self.symbol['ucolor']).GetColour()))
2325        UserSettings.Set(group='gcpman', key='symbol', subkey='unused',
2326                         value=self.showunused.GetValue())
2327        UserSettings.Set(
2328            group='gcpman',
2329            key='symbol',
2330            subkey='size',
2331            value=wx.FindWindowById(
2332                self.symbol['size']).GetValue())
2333        UserSettings.Set(
2334            group='gcpman',
2335            key='symbol',
2336            subkey='width',
2337            value=wx.FindWindowById(
2338                self.symbol['width']).GetValue())
2339
2340        srcrender = False
2341        tgtrender = False
2342        reload_target = False
2343        if self.new_src_map != src_map:
2344            # remove old layer
2345            layers = self.parent.grwiz.SrcMap.GetListOfLayers()
2346            self.parent.grwiz.SrcMap.DeleteLayer(layers[0])
2347
2348            src_map = self.new_src_map
2349            if maptype == 'raster':
2350                cmdlist = ['d.rast', 'map=%s' % src_map]
2351                srcrender = True
2352            self.parent.grwiz.SwitchEnv('source')
2353            name, found = utils.GetLayerNameFromCmd(cmdlist)
2354            self.parent.grwiz.SrcMap.AddLayer(
2355                ltype=maptype, command=cmdlist, active=True, name=name,
2356                hidden=False, opacity=1.0, render=False)
2357
2358            self.parent.grwiz.SwitchEnv('target')
2359
2360        if self.new_tgt_map['raster'] != tgt_map['raster']:
2361            # remove all layers
2362            layers = self.parent.grwiz.TgtMap.GetListOfLayers()
2363            while layers:
2364                self.parent.grwiz.TgtMap.DeleteLayer(layers[0])
2365                del layers[0]
2366                layers = self.parent.grwiz.TgtMap.GetListOfLayers()
2367            # self.parent.grwiz.TgtMap.DeleteAllLayers()
2368            reload_target = True
2369            tgt_map['raster'] = self.new_tgt_map['raster']
2370
2371            if tgt_map['raster'] != '':
2372                cmdlist = ['d.rast', 'map=%s' % tgt_map['raster']]
2373                name, found = utils.GetLayerNameFromCmd(cmdlist)
2374                self.parent.grwiz.TgtMap.AddLayer(
2375                    ltype='raster', command=cmdlist, active=True, name=name,
2376                    hidden=False, opacity=1.0, render=False)
2377
2378                tgtrender = True
2379
2380        if tgt_map['raster'] == '':
2381            if self.parent.show_target == True:
2382                self.parent.show_target = False
2383                self.parent._mgr.GetPane("target").Hide()
2384                self.parent._mgr.Update()
2385                self.parent.activemap.SetSelection(0)
2386                self.parent.activemap.Enable(False)
2387                self.parent.GetMapToolbar().Enable('zoommenu', enable=False)
2388        else:
2389            if self.parent.show_target == False:
2390                self.parent.show_target = True
2391                self.parent._mgr.GetPane("target").Show()
2392                self.parent._mgr.Update()
2393                self.parent.activemap.SetSelection(0)
2394                self.parent.activemap.Enable(True)
2395                self.parent.GetMapToolbar().Enable('zoommenu', enable=True)
2396                self.parent.TgtMapWindow.ZoomToMap(
2397                    layers=self.parent.TgtMap.GetListOfLayers())
2398
2399        self.parent.UpdateColours(
2400            srcrender,
2401            tgtrender)
2402        self.parent.SetSettings()
2403
2404    def OnSave(self, event):
2405        """Button 'Save' pressed"""
2406        self.UpdateSettings()
2407        fileSettings = {}
2408        UserSettings.ReadSettingsFile(settings=fileSettings)
2409        fileSettings['gcpman'] = UserSettings.Get(group='gcpman')
2410        file = UserSettings.SaveToFile(fileSettings)
2411        self.parent._giface.WriteLog(
2412            _('GCP Manager settings saved to file \'%s\'.') %
2413            file)
2414        # self.Close()
2415
2416    def OnApply(self, event):
2417        """Button 'Apply' pressed"""
2418        self.UpdateSettings()
2419        # self.Close()
2420
2421    def OnClose(self, event):
2422        """Button 'Cancel' pressed"""
2423        self.Close()
2424