Browse Source

wxGUI location wizard: EPSG page improvements (#670)

* Widget to epsg database file was removed.
* EPSG field was removed, instead it uses search box to filter items in the list of projections. It automatically selects first item in the list unless there exists a complete match with EPSG number.
* Link to epsg.io was added. This link responds to the search text.
* Includes changes in layout to avoid always using GridBagSizer for every page of the wizard.
* Some changes in wording.

Co-authored-by: Anna Petrasova <kratochanna@gmail.com>
Linda Kladivova 4 years ago
parent
commit
19ea788f92
1 changed files with 150 additions and 126 deletions
  1. 150 126
      gui/wxpython/location_wizard/wizard.py

+ 150 - 126
gui/wxpython/location_wizard/wizard.py

@@ -55,7 +55,7 @@ from core.utils import cmp
 from core.gcmd import RunCommand, GError, GMessage, GWarning
 from gui_core.widgets import GenericValidator
 from gui_core.wrap import SpinCtrl, SearchCtrl, StaticText, \
-    TextCtrl, Button, CheckBox, StaticBox, NewId, ListCtrl
+    TextCtrl, Button, CheckBox, StaticBox, NewId, ListCtrl, HyperlinkCtrl
 from location_wizard.base import BaseClass
 from location_wizard.dialogs import SelectTransformDialog
 
@@ -87,12 +87,10 @@ class TitledPage(WizardPageSimple):
         self.title.SetFont(wx.Font(13, wx.SWISS, wx.NORMAL, wx.BOLD))
         # main sizers
         self.pagesizer = wx.BoxSizer(wx.VERTICAL)
-        self.sizer = wx.GridBagSizer(vgap=0, hgap=0)
-        self.sizer.SetCols(5)
-        self.sizer.SetRows(8)
 
     def DoLayout(self):
         """Do page layout"""
+        
         self.pagesizer.Add(self.title, proportion=0,
                            flag=wx.EXPAND | wx.ALL,
                            border=5)
@@ -158,6 +156,10 @@ class DatabasePage(TitledPage):
         TitledPage.__init__(self, wizard, _(
             "Define new GRASS Location"))
 
+        self.sizer = wx.GridBagSizer(vgap=0, hgap=0)
+        self.sizer.SetCols(5)
+        self.sizer.SetRows(8)
+        
         self.grassdatabase = grassdatabase
         self.location = ''
         self.locTitle = ''
@@ -317,13 +319,17 @@ class CoordinateSystemPage(TitledPage):
     def __init__(self, wizard, parent):
         TitledPage.__init__(self, wizard, _(
             "Choose method for creating a new location"))
-
+        
+        self.sizer = wx.GridBagSizer(vgap=0, hgap=0)
+        self.sizer.SetCols(5)
+        self.sizer.SetRows(8)
+        
         self.parent = parent
         global coordsys
 
         # toggles
         self.radioEpsg = wx.RadioButton(parent=self, id=wx.ID_ANY, label=_(
-            "Select EPSG code of spatial reference system"), style=wx.RB_GROUP)
+            "Select coordinate reference system by EPSG"), style=wx.RB_GROUP)
         #self.radioIau = wx.RadioButton(
         #    parent=self, id=wx.ID_ANY,
         #    label=_("Select IAU code of spatial reference system"))
@@ -462,7 +468,11 @@ class ProjectionsPage(TitledPage):
 
     def __init__(self, wizard, parent):
         TitledPage.__init__(self, wizard, _("Choose projection"))
-
+        
+        self.sizer = wx.GridBagSizer(vgap=0, hgap=0)
+        self.sizer.SetCols(5)
+        self.sizer.SetRows(8)
+        
         self.parent = parent
         self.proj = ''
         self.projdesc = ''
@@ -637,7 +647,8 @@ class ItemList(ListCtrl,
         self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColumnClick)
 
     def Populate(self, data=None, update=False):
-        """Populate list"""
+        """Populate and sort list.
+        Returns sorted list."""
         self.itemDataMap = {}
         self.itemIndexMap = []
 
@@ -664,6 +675,7 @@ class ItemList(ListCtrl,
             self.SetColumnWidth(1, 300)
 
             self.SendSizeEvent()
+            return data
 
         except Exception as e:
             wx.MessageBox(parent=self,
@@ -745,13 +757,14 @@ class ItemList(ListCtrl,
         """Used by listmix.ColumnSorterMixin"""
         return self
 
-    def Search(self, index, pattern):
+    def Search(self, index, pattern, firstOnly=True):
         """Search projection by description
-        Return first found item or None
+        Return first found item (or None) if firstOnly is True,
+        all data (or empty list) if False
         """
         if pattern == '':
             self.Populate(self.sourceData)
-            return []
+            return None if firstOnly else []
 
         data = []
         pattern = pattern.lower()
@@ -766,11 +779,17 @@ class ItemList(ListCtrl,
                     # osgeo4w problem (should be fixed)
                     pass
 
-        self.Populate(data)
+        data = self.Populate(data)
         if len(data) > 0:
-            return data[0]
+            if firstOnly:
+                return data[0]
+            else:
+                return data
         else:
-            return []
+            if firstOnly:
+                return None
+            else:
+                return []
 
 
 class ProjParamsPage(TitledPage):
@@ -782,6 +801,10 @@ class ProjParamsPage(TitledPage):
         TitledPage.__init__(self, wizard, _("Choose projection parameters"))
         global coordsys
 
+        self.sizer = wx.GridBagSizer(vgap=0, hgap=0)
+        self.sizer.SetCols(5)
+        self.sizer.SetRows(8)
+        
         self.parent = parent
         self.panel = None
         self.prjParamSizer = None
@@ -989,6 +1012,10 @@ class DatumPage(TitledPage):
     def __init__(self, wizard, parent):
         TitledPage.__init__(self, wizard, _("Specify geodetic datum"))
 
+        self.sizer = wx.GridBagSizer(vgap=0, hgap=0)
+        self.sizer.SetCols(5)
+        self.sizer.SetRows(8)
+        
         self.parent = parent
         self.datum = ''
         self.datumdesc = ''
@@ -1162,7 +1189,11 @@ class EllipsePage(TitledPage):
 
     def __init__(self, wizard, parent):
         TitledPage.__init__(self, wizard, _("Specify ellipsoid"))
-
+        
+        self.sizer = wx.GridBagSizer(vgap=0, hgap=0)
+        self.sizer.SetCols(5)
+        self.sizer.SetRows(8)
+        
         self.parent = parent
 
         self.ellipse = ''
@@ -1337,7 +1368,11 @@ class GeoreferencedFilePage(TitledPage):
 
     def __init__(self, wizard, parent):
         TitledPage.__init__(self, wizard, _("Select georeferenced file"))
-
+        
+        self.sizer = wx.GridBagSizer(vgap=0, hgap=0)
+        self.sizer.SetCols(5)
+        self.sizer.SetRows(8)
+        
         self.georeffile = ''
 
         # create controls
@@ -1413,7 +1448,11 @@ class WKTPage(TitledPage):
     def __init__(self, wizard, parent):
         TitledPage.__init__(self, wizard, _(
             "Select Well Known Text (WKT) .prj file"))
-
+        
+        self.sizer = wx.GridBagSizer(vgap=0, hgap=0)
+        self.sizer.SetCols(5)
+        self.sizer.SetRows(8)
+        
         self.wktfile = ''
 
         # create controls
@@ -1489,6 +1528,12 @@ class EPSGPage(TitledPage):
 
     def __init__(self, wizard, parent):
         TitledPage.__init__(self, wizard, _("Choose EPSG Code"))
+        
+        self.sizer = wx.BoxSizer(wx.VERTICAL)  
+        searchBoxSizer = wx.BoxSizer(wx.HORIZONTAL)
+        epsglistBoxSizer = wx.BoxSizer(wx.HORIZONTAL)
+        informationBoxSizer = wx.BoxSizer(wx.HORIZONTAL)
+        
         self.parent = parent
         self.epsgCodeDict = {}
         self.epsgcode = None
@@ -1496,25 +1541,16 @@ class EPSGPage(TitledPage):
         self.epsgparams = ''
 
         # labels
-        self.lfile = self.MakeLabel(
-            _("Path to the EPSG-codes file:"),
-            style=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL)
         self.lcode = self.MakeLabel(
-            _("EPSG code:"),
+            _("Filter by EPSG code or description:"),
+            style=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL)
+        self.llink = self.MakeLabel(
+            _("Find more information at:"),
             style=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL)
-        # text input
-        epsgdir = utils.PathJoin(os.environ["GRASS_PROJSHARE"], 'epsg')
-        self.tfile = self.MakeTextCtrl(text=epsgdir, size=(200, -1),
-                                       style=wx.TE_PROCESS_ENTER)
-        self.tcode = self.MakeTextCtrl(size=(200, -1))
-
-        # buttons
-        self.bbrowse = self.MakeButton(_("Browse"))
 
         # search box
-        self.searchb = SearchCtrl(self, size=(200, -1),
-                                  style=wx.TE_PROCESS_ENTER)
-
+        self.searchb = SearchCtrl(self, size=(-1, 30),
+                                  style=wx.TE_PROCESS_ENTER)        
         self.epsglist = ItemList(
             self,
             data=None,
@@ -1523,44 +1559,44 @@ class EPSGPage(TitledPage):
                 _('Description'),
                 _('Parameters')])
 
-        # layout
-        self.sizer.Add(self.lfile,
-                       flag=wx.ALIGN_LEFT |
-                       wx.ALIGN_CENTER_VERTICAL |
-                       wx.ALL, border=5, pos=(1, 1), span=(1, 2))
-        self.sizer.Add(self.tfile,
-                       flag=wx.ALIGN_LEFT |
-                       wx.ALIGN_CENTER_VERTICAL |
-                       wx.ALL, border=5, pos=(1, 3))
-        self.sizer.Add(self.bbrowse,
-                       flag=wx.ALIGN_LEFT |
-                       wx.ALIGN_CENTER_VERTICAL |
-                       wx.ALL, border=5, pos=(1, 4))
-        self.sizer.Add(self.lcode,
-                       flag=wx.ALIGN_LEFT |
-                       wx.ALIGN_CENTER_VERTICAL |
-                       wx.ALL, border=5, pos=(2, 1), span=(1, 2))
-        self.sizer.Add(self.tcode,
-                       flag=wx.ALIGN_LEFT |
-                       wx.ALIGN_CENTER_VERTICAL |
-                       wx.ALL, border=5, pos=(2, 3))
-        self.sizer.Add(self.searchb,
-                       flag=wx.ALIGN_LEFT |
-                       wx.ALIGN_CENTER_VERTICAL |
-                       wx.ALL, border=5, pos=(3, 3))
-
-        self.sizer.Add(self.epsglist,
-                       flag=wx.ALIGN_LEFT | wx.EXPAND, pos=(4, 1),
-                       span=(1, 4))
-        self.sizer.AddGrowableCol(3)
-        self.sizer.AddGrowableRow(4)
+        # epsg.io hyperlink
+        self.tlink = HyperlinkCtrl(
+            self, id=wx.ID_ANY, 
+            label="epsg.io",
+            url="https://epsg.io/")
+        self.tlink.SetNormalColour(
+            wx.SystemSettings.GetColour(
+                wx.SYS_COLOUR_GRAYTEXT))
+        self.tlink.SetVisitedColour(
+            wx.SystemSettings.GetColour(
+                wx.SYS_COLOUR_GRAYTEXT))
+        
+        # layout       
+        searchBoxSizer.Add(self.lcode, proportion=0,
+                       flag=wx.ALIGN_CENTER_VERTICAL |
+                       wx.ALL, border=5)
+        searchBoxSizer.Add(self.searchb, proportion=1,
+                       flag=wx.ALL |
+                       wx.EXPAND, border=5)
+        epsglistBoxSizer.Add(self.epsglist, proportion=1,
+                       flag=wx.ALL |
+                       wx.EXPAND, border=5)
+        informationBoxSizer.AddStretchSpacer(1) 
+        informationBoxSizer.Add(self.llink,  proportion=0,
+                       flag=wx.ALIGN_CENTER_VERTICAL |
+                       wx.RIGHT, border=5)
+        informationBoxSizer.Add(self.tlink, proportion=0,
+                       flag=wx.ALIGN_CENTER_VERTICAL)
 
+        
+        self.sizer.Add(searchBoxSizer, proportion=0, flag=wx.EXPAND)
+        self.sizer.Add(epsglistBoxSizer, proportion=1, flag=wx.EXPAND)
+        self.sizer.Add(informationBoxSizer, proportion=0,
+                       flag=wx.EXPAND | wx.TOP, border=5)
+        
         # events
-        self.bbrowse.Bind(wx.EVT_BUTTON, self.OnBrowse)
-        self.tfile.Bind(wx.EVT_TEXT_ENTER, self.OnBrowseCodes)
-        self.tcode.Bind(wx.EVT_TEXT, self.OnText)
+        self.searchb.Bind(wx.EVT_TEXT, self.OnTextChange)
         self.epsglist.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected)
-        self.searchb.Bind(wx.EVT_TEXT_ENTER, self.OnSearch)
         self.Bind(wiz.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
         self.Bind(wiz.EVT_WIZARD_PAGE_CHANGED, self.OnEnterPage)
 
@@ -1569,12 +1605,11 @@ class EPSGPage(TitledPage):
         if event.GetDirection():
             if not self.epsgcode:
                 # disable 'next' button by default
-                wx.FindWindowById(wx.ID_FORWARD).Enable(False)
+                self.EnableNext(False)
+                # load default epsg database file
+                self.OnBrowseCodes(None)
             else:
-                wx.FindWindowById(wx.ID_FORWARD).Enable(True)
-
-        # load default epsg database file
-        self.OnBrowseCodes(None)
+                self.EnableNext(True)
 
         event.Skip()
 
@@ -1611,78 +1646,52 @@ class EPSGPage(TitledPage):
                     self.parent.datum_trans = dtrans
             self.GetNext().SetPrev(self)
 
-    def OnText(self, event):
-        self.epsgcode = event.GetString()
-        try:
-            self.epsgcode = int(self.epsgcode)
-        except:
-            self.epsgcode = None
-
+    def EnableNext(self, enable=True):
         nextButton = wx.FindWindowById(wx.ID_FORWARD)
+        nextButton.Enable(enable)
 
-        if self.epsgcode and self.epsgCodeDict and \
-                self.epsgcode in self.epsgCodeDict.keys():
-            self.epsgdesc = self.epsgCodeDict[self.epsgcode][0]
-            self.epsgparams = self.epsgCodeDict[self.epsgcode][1]
-            if not nextButton.IsEnabled():
-                nextButton.Enable(True)
-        else:
-            self.epsgcode = None  # not found
-            if nextButton.IsEnabled():
-                nextButton.Enable(False)
-            self.epsgdesc = self.epsgparams = ''
-
-    def OnSearch(self, event):
+    def OnTextChange(self, event):
         value = self.searchb.GetValue()
-
         if value == '':
+            self.tlink.SetURL(str("https://epsg.io/"))
             self.epsgcode = None
             self.epsgdesc = self.epsgparams = ''
-            self.tcode.SetValue('')
-            self.searchb.SetValue('')
             self.OnBrowseCodes(None)
+            self.EnableNext(False)
         else:
-            try:
-                self.epsgcode, self.epsgdesc, self.epsgparams = \
-                    self.epsglist.Search(index=[0, 1, 2], pattern=value)
-            except (IndexError, ValueError):  # -> no item found
+            self.tlink.SetURL(str("https://epsg.io/?q={0}".format(value)))
+            data = self.epsglist.Search(index=[0, 1, 2], pattern=value,
+                                        firstOnly=False)
+            if data:
+                index = 0
+                # search for the exact epsg code match
+                # otherwise just select first item
+                try:
+                    epsg = int(value)
+                    for i, (code, desc, params) in enumerate(data):
+                        if code == epsg:
+                            index = i
+                            break
+                except ValueError:
+                    pass
+                self.epsgcode, self.epsgdesc, self.epsgparams = data[index]
+                self.epsglist.Select(index)
+                self.epsglist.Focus(index)
+                self.EnableNext()
+            else:
                 self.epsgcode = None
                 self.epsgdesc = self.epsgparams = ''
-                self.tcode.SetValue('')
-
-        event.Skip()
-
-    def OnBrowse(self, event):
-        """Define path for EPSG code file"""
-        path = os.path.dirname(self.tfile.GetValue())
-        if not path:
-            path = os.getcwd()
-
-        dlg = wx.FileDialog(
-            parent=self,
-            message=_("Choose EPSG codes file"),
-            defaultDir=path,
-            defaultFile="",
-            wildcard="*",
-            style=wx.FD_OPEN)
-
-        if dlg.ShowModal() == wx.ID_OK:
-            path = dlg.GetPath()
-            self.tfile.SetValue(path)
-            self.OnBrowseCodes(None)
-
-        dlg.Destroy()
+                self.EnableNext(False)
 
         event.Skip()
 
     def OnItemSelected(self, event):
         """EPSG code selected from the list"""
         index = event.GetIndex()
-        item = event.GetItem()
 
         self.epsgcode = int(self.epsglist.GetItem(index, 0).GetText())
         self.epsgdesc = self.epsglist.GetItem(index, 1).GetText()
-        self.tcode.SetValue(str(self.epsgcode))
+        self.EnableNext(True)
 
         event.Skip()
 
@@ -1712,6 +1721,11 @@ class IAUPage(TitledPage):
 
     def __init__(self, wizard, parent):
         TitledPage.__init__(self, wizard, _("Choose IAU Code"))
+        
+        self.sizer = wx.GridBagSizer(vgap=0, hgap=0)
+        self.sizer.SetCols(5)
+        self.sizer.SetRows(8)
+        
         self.parent = parent
         self.epsgCodeDict = {}
         self.epsgcode = None
@@ -1961,6 +1975,11 @@ class CustomPage(TitledPage):
             self, wizard,
             _("Choose method of specifying georeferencing parameters"))
         global coordsys
+        
+        self.sizer = wx.GridBagSizer(vgap=0, hgap=0)
+        self.sizer.SetCols(5)
+        self.sizer.SetRows(8)
+        
         self.customstring = ''
         self.parent = parent
 
@@ -2069,8 +2088,13 @@ class SummaryPage(TitledPage):
 
     def __init__(self, wizard, parent):
         TitledPage.__init__(self, wizard, _("Summary"))
-        self.parent = parent
 
+        self.sizer = wx.GridBagSizer(vgap=0, hgap=0)
+        self.sizer.SetCols(5)
+        self.sizer.SetRows(8)
+        
+        self.parent = parent
+        
         self.panelTitle = scrolled.ScrolledPanel(parent=self, id=wx.ID_ANY)
         self.panelProj4string = scrolled.ScrolledPanel(
             parent=self, id=wx.ID_ANY)