mapdisp.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  1. """!
  2. @package gui_core.mapdisp
  3. @brief Base classes for Map display window
  4. Classes:
  5. - mapdisp::MapFrameBase
  6. - mapdisp::SingleMapFrame
  7. - mapdisp::DoubleMapFrame
  8. (C) 2009-2011 by the GRASS Development Team
  9. This program is free software under the GNU General Public License
  10. (>=v2). Read the file COPYING that comes with GRASS for details.
  11. @author Martin Landa <landa.martin gmail.com>
  12. @author Michael Barton <michael.barton@asu.edu>
  13. @author Vaclav Petras <wenzeslaus gmail.com>
  14. @author Anna Kratochvilova <kratochanna gmail.com>
  15. """
  16. import os
  17. import wx
  18. import wx.aui
  19. from core import globalvar
  20. from core.debug import Debug
  21. from grass.script import core as grass
  22. class MapFrameBase(wx.Frame):
  23. """!Base class for map display window
  24. Derived class must use (create and initialize) \c statusbarManager
  25. or override
  26. GetProperty(), SetProperty() and HasProperty() methods.
  27. Several methods has to be overriden or
  28. \c NotImplementedError("MethodName") will be raised.
  29. If derived class enables and disables auto-rendering,
  30. it should override IsAutoRendered method.
  31. Derived class can has one or more map windows (and map renderes)
  32. but implementation of MapFrameBase expects that one window and
  33. one map will be current.
  34. Current instances of map window and map renderer should be returned
  35. by methods GetWindow() and GetMap() respectively.
  36. AUI manager is stored in \c self._mgr.
  37. """
  38. def __init__(self, parent = None, id = wx.ID_ANY, title = None,
  39. style = wx.DEFAULT_FRAME_STYLE,
  40. auimgr = None, name = None, **kwargs):
  41. """!
  42. @warning Use \a auimgr parameter only if you know what you are doing.
  43. @param parent gui parent
  44. @param id wx id
  45. @param title window title
  46. @param style \c wx.Frame style
  47. @param toolbars array of activated toolbars, e.g. ['map', 'digit']
  48. @param auimgr AUI manager (if \c None, wx.aui.AuiManager is used)
  49. @param name frame name
  50. @param kwargs arguments passed to \c wx.Frame
  51. """
  52. self.parent = parent
  53. wx.Frame.__init__(self, parent, id, title, style = style, name = name, **kwargs)
  54. # available cursors
  55. self.cursors = {
  56. # default: cross
  57. # "default" : wx.StockCursor(wx.CURSOR_DEFAULT),
  58. "default" : wx.StockCursor(wx.CURSOR_ARROW),
  59. "cross" : wx.StockCursor(wx.CURSOR_CROSS),
  60. "hand" : wx.StockCursor(wx.CURSOR_HAND),
  61. "pencil" : wx.StockCursor(wx.CURSOR_PENCIL),
  62. "sizenwse": wx.StockCursor(wx.CURSOR_SIZENWSE)
  63. }
  64. #
  65. # set the size & system icon
  66. #
  67. self.SetClientSize(self.GetSize())
  68. self.iconsize = (16, 16)
  69. self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass_map.ico'), wx.BITMAP_TYPE_ICO))
  70. # toolbars
  71. self.toolbars = {}
  72. #
  73. # Fancy gui
  74. #
  75. if auimgr == None:
  76. self._mgr = wx.aui.AuiManager(self)
  77. else:
  78. self._mgr = auimgr
  79. def _initMap(self, Map):
  80. """!Initialize map display, set dimensions and map region
  81. """
  82. if not grass.find_program('g.region', ['--help']):
  83. sys.exit(_("GRASS module '%s' not found. Unable to start map "
  84. "display window.") % 'g.region')
  85. self.width, self.height = self.GetClientSize()
  86. Debug.msg(2, "MapFrame._initMap():")
  87. Map.ChangeMapSize(self.GetClientSize())
  88. Map.region = Map.GetRegion() # g.region -upgc
  89. # self.Map.SetRegion() # adjust region to match display window
  90. def SetProperty(self, name, value):
  91. """!Sets property"""
  92. self.statusbarManager.SetProperty(name, value)
  93. def GetProperty(self, name):
  94. """!Returns property"""
  95. return self.statusbarManager.GetProperty(name)
  96. def HasProperty(self, name):
  97. """!Checks whether object has property"""
  98. return self.statusbarManager.HasProperty(name)
  99. def GetPPM(self):
  100. """! Get pixel per meter
  101. @todo now computed every time, is it necessary?
  102. @todo enable user to specify ppm (and store it in UserSettings)
  103. """
  104. # TODO: need to be fixed...
  105. ### screen X region problem
  106. ### user should specify ppm
  107. dc = wx.ScreenDC()
  108. dpSizePx = wx.DisplaySize() # display size in pixels
  109. dpSizeMM = wx.DisplaySizeMM() # display size in mm (system)
  110. dpSizeIn = (dpSizeMM[0] / 25.4, dpSizeMM[1] / 25.4) # inches
  111. sysPpi = dc.GetPPI()
  112. comPpi = (dpSizePx[0] / dpSizeIn[0],
  113. dpSizePx[1] / dpSizeIn[1])
  114. ppi = comPpi # pixel per inch
  115. ppm = ((ppi[0] / 2.54) * 100, # pixel per meter
  116. (ppi[1] / 2.54) * 100)
  117. Debug.msg(4, "MapFrameBase.GetPPM(): size: px=%d,%d mm=%f,%f "
  118. "in=%f,%f ppi: sys=%d,%d com=%d,%d; ppm=%f,%f" % \
  119. (dpSizePx[0], dpSizePx[1], dpSizeMM[0], dpSizeMM[1],
  120. dpSizeIn[0], dpSizeIn[1],
  121. sysPpi[0], sysPpi[1], comPpi[0], comPpi[1],
  122. ppm[0], ppm[1]))
  123. return ppm
  124. def SetMapScale(self, value, map = None):
  125. """! Set current map scale
  126. @param value scale value (n if scale is 1:n)
  127. @param map Map instance (if none self.Map is used)
  128. """
  129. if not map:
  130. map = self.Map
  131. region = self.Map.region
  132. dEW = value * (region['cols'] / self.GetPPM()[0])
  133. dNS = value * (region['rows'] / self.GetPPM()[1])
  134. region['n'] = region['center_northing'] + dNS / 2.
  135. region['s'] = region['center_northing'] - dNS / 2.
  136. region['w'] = region['center_easting'] - dEW / 2.
  137. region['e'] = region['center_easting'] + dEW / 2.
  138. # add to zoom history
  139. self.GetWindow().ZoomHistory(region['n'], region['s'],
  140. region['e'], region['w'])
  141. def GetMapScale(self, map = None):
  142. """! Get current map scale
  143. @param map Map instance (if none self.Map is used)
  144. """
  145. if not map:
  146. map = self.GetMap()
  147. region = map.region
  148. ppm = self.GetPPM()
  149. heightCm = region['rows'] / ppm[1] * 100
  150. widthCm = region['cols'] / ppm[0] * 100
  151. Debug.msg(4, "MapFrame.GetMapScale(): width_cm=%f, height_cm=%f" %
  152. (widthCm, heightCm))
  153. xscale = (region['e'] - region['w']) / (region['cols'] / ppm[0])
  154. yscale = (region['n'] - region['s']) / (region['rows'] / ppm[1])
  155. scale = (xscale + yscale) / 2.
  156. Debug.msg(3, "MapFrame.GetMapScale(): xscale=%f, yscale=%f -> scale=%f" % \
  157. (xscale, yscale, scale))
  158. return scale
  159. def GetProgressBar(self):
  160. """!Returns progress bar
  161. Progress bar can be used by other classes.
  162. """
  163. return self.statusbarManager.GetProgressBar()
  164. def GetMap(self):
  165. """!Returns current map (renderer) instance"""
  166. raise NotImplementedError("GetMap")
  167. def GetWindow(self):
  168. """!Returns current map window"""
  169. raise NotImplementedError("GetWindow")
  170. def GetMapToolbar(self):
  171. """!Returns toolbar with zooming tools"""
  172. raise NotImplementedError("GetMapToolbar")
  173. def GetToolbar(self, name):
  174. """!Returns toolbar if exists else None.
  175. Toolbars dictionary contains currently used toolbars only.
  176. """
  177. if name in self.toolbars:
  178. return self.toolbars[name]
  179. return None
  180. def StatusbarUpdate(self):
  181. """!Update statusbar content"""
  182. self.statusbarManager.Update()
  183. def IsAutoRendered(self):
  184. """!Check if auto-rendering is enabled"""
  185. return self.GetProperty('render')
  186. def CoordinatesChanged(self):
  187. """!Shows current coordinates on statusbar.
  188. Used in BufferedWindow to report change of map coordinates (under mouse cursor).
  189. """
  190. self.statusbarManager.ShowItem('coordinates')
  191. def StatusbarReposition(self):
  192. """!Reposition items in statusbar"""
  193. self.statusbarManager.Reposition()
  194. def StatusbarEnableLongHelp(self, enable = True):
  195. """!Enable/disable toolbars long help"""
  196. for toolbar in self.toolbars.itervalues():
  197. toolbar.EnableLongHelp(enable)
  198. def IsStandalone(self):
  199. """!Check if map frame is standalone"""
  200. raise NotImplementedError("IsStandalone")
  201. def OnRender(self, event):
  202. """!Re-render map composition (each map layer)
  203. """
  204. raise NotImplementedError("OnRender")
  205. def OnDraw(self, event):
  206. """!Re-display current map composition
  207. """
  208. self.MapWindow.UpdateMap(render = False)
  209. def OnErase(self, event):
  210. """!Erase the canvas
  211. """
  212. self.MapWindow.EraseMap()
  213. def OnZoomIn(self, event):
  214. """!Zoom in the map.
  215. Set mouse cursor, zoombox attributes, and zoom direction
  216. """
  217. toolbar = self.GetMapToolbar()
  218. self._switchTool(toolbar, event)
  219. win = self.GetWindow()
  220. self._prepareZoom(mapWindow = win, zoomType = 1)
  221. def OnZoomOut(self, event):
  222. """!Zoom out the map.
  223. Set mouse cursor, zoombox attributes, and zoom direction
  224. """
  225. toolbar = self.GetMapToolbar()
  226. self._switchTool(toolbar, event)
  227. win = self.GetWindow()
  228. self._prepareZoom(mapWindow = win, zoomType = -1)
  229. def _prepareZoom(self, mapWindow, zoomType):
  230. """!Prepares MapWindow for zoom, toggles toolbar
  231. @param mapWindow MapWindow to prepare
  232. @param zoomType 1 for zoom in, -1 for zoom out
  233. """
  234. mapWindow.mouse['use'] = "zoom"
  235. mapWindow.mouse['box'] = "box"
  236. mapWindow.zoomtype = zoomType
  237. mapWindow.pen = wx.Pen(colour = 'Red', width = 2, style = wx.SHORT_DASH)
  238. # change the cursor
  239. mapWindow.SetCursor(self.cursors["cross"])
  240. def _switchTool(self, toolbar, event):
  241. """!Helper function to switch tools"""
  242. if toolbar:
  243. toolbar.OnTool(event)
  244. toolbar.action['desc'] = ''
  245. def OnPan(self, event):
  246. """!Panning, set mouse to drag
  247. """
  248. toolbar = self.GetMapToolbar()
  249. self._switchTool(toolbar, event)
  250. win = self.GetWindow()
  251. self._preparePan(mapWindow = win)
  252. def _preparePan(self, mapWindow):
  253. """!Prepares MapWindow for pan, toggles toolbar
  254. @param mapWindow MapWindow to prepare
  255. """
  256. mapWindow.mouse['use'] = "pan"
  257. mapWindow.mouse['box'] = "pan"
  258. mapWindow.zoomtype = 0
  259. # change the cursor
  260. mapWindow.SetCursor(self.cursors["hand"])
  261. def OnZoomBack(self, event):
  262. """!Zoom last (previously stored position)
  263. """
  264. self.MapWindow.ZoomBack()
  265. def OnZoomToMap(self, event):
  266. """!
  267. Set display extents to match selected raster (including NULLs)
  268. or vector map.
  269. """
  270. self.MapWindow.ZoomToMap(layers = self.Map.GetListOfLayers())
  271. def OnZoomToWind(self, event):
  272. """!Set display geometry to match computational region
  273. settings (set with g.region)
  274. """
  275. self.MapWindow.ZoomToWind()
  276. def OnZoomToDefault(self, event):
  277. """!Set display geometry to match default region settings
  278. """
  279. self.MapWindow.ZoomToDefault()
  280. class SingleMapFrame(MapFrameBase):
  281. """! Frame with one map window.
  282. It is base class for frames which needs only one map.
  283. Derived class should have \c self.MapWindow or
  284. it has to override GetWindow() methods.
  285. @note To access maps use getters only
  286. (when using class or when writing class itself).
  287. """
  288. def __init__(self, parent = None, id = wx.ID_ANY, title = None,
  289. style = wx.DEFAULT_FRAME_STYLE,
  290. Map = None,
  291. auimgr = None, name = None, **kwargs):
  292. """!
  293. @param parent gui parent
  294. @param id wx id
  295. @param title window title
  296. @param style \c wx.Frame style
  297. @param Map instance of render.Map
  298. @param name frame name
  299. @param kwargs arguments passed to MapFrameBase
  300. """
  301. MapFrameBase.__init__(self, parent = parent, id = id, title = title,
  302. style = style,
  303. auimgr = auimgr, name = name, **kwargs)
  304. self.Map = Map # instance of render.Map
  305. #
  306. # initialize region values
  307. #
  308. self._initMap(Map = self.Map)
  309. def GetMap(self):
  310. """!Returns map (renderer) instance"""
  311. return self.Map
  312. def GetWindow(self):
  313. """!Returns map window"""
  314. return self.MapWindow
  315. def OnRender(self, event):
  316. """!Re-render map composition (each map layer)
  317. """
  318. self.GetWindow().UpdateMap(render = True, renderVector = True)
  319. # update statusbar
  320. self.StatusbarUpdate()
  321. class DoubleMapFrame(MapFrameBase):
  322. """! Frame with two map windows.
  323. It is base class for frames which needs two maps.
  324. There is no primary and secondary map. Both maps are equal.
  325. However, one map is current.
  326. It is expected that derived class will call _bindWindowsActivation()
  327. when both map windows will be initialized.
  328. Drived class should have method GetMapToolbar() returns toolbar
  329. which has method SetActiveMap().
  330. @note To access maps use getters only
  331. (when using class or when writing class itself).
  332. @todo Use it in GCP manager
  333. (probably changes to both DoubleMapFrame and GCP MapFrame will be neccessary).
  334. """
  335. def __init__(self, parent = None, id = wx.ID_ANY, title = None,
  336. style = wx.DEFAULT_FRAME_STYLE,
  337. firstMap = None, secondMap = None,
  338. auimgr = None, name = None, **kwargs):
  339. """!
  340. \a firstMap is set as active (by assign it to \c self.Map).
  341. Derived class should assging to \c self.MapWindow to make one
  342. map window current by dafault.
  343. @param parent gui parent
  344. @param id wx id
  345. @param title window title
  346. @param style \c wx.Frame style
  347. @param name frame name
  348. @param kwargs arguments passed to MapFrameBase
  349. """
  350. MapFrameBase.__init__(self, parent = parent, id = id, title = title,
  351. style = style,
  352. auimgr = auimgr, name = name, **kwargs)
  353. self.firstMap = firstMap
  354. self.secondMap = secondMap
  355. self.Map = firstMap
  356. #
  357. # initialize region values
  358. #
  359. self._initMap(Map = self.firstMap)
  360. self._initMap(Map = self.secondMap)
  361. def _bindWindowsActivation(self):
  362. self.GetFirstWindow().Bind(wx.EVT_ENTER_WINDOW, self.ActivateFirstMap)
  363. self.GetSecondWindow().Bind(wx.EVT_ENTER_WINDOW, self.ActivateSecondMap)
  364. def GetFirstMap(self):
  365. """!Returns first Map instance
  366. """
  367. return self.firstMap
  368. def GetSecondMap(self):
  369. """!Returns second Map instance
  370. """
  371. return self.secondMap
  372. def GetFirstWindow(self):
  373. """!Get first map window"""
  374. return self.firstMapWindow
  375. def GetSecondWindow(self):
  376. """!Get second map window"""
  377. return self.secondMapWindow
  378. def GetMap(self):
  379. """!Returns current map (renderer) instance
  380. @note Use this method to access current map renderer.
  381. (It is not guarented that current map will be stored in
  382. \c self.Map in future versions.)
  383. """
  384. return self.Map
  385. def GetWindow(self):
  386. """!Returns current map window
  387. @see GetMap()
  388. """
  389. return self.MapWindow
  390. def ActivateFirstMap(self, event = None):
  391. """!Make first Map and MapWindow active"""
  392. self.Map = self.firstMap
  393. self.MapWindow = self.firstMapWindow
  394. self.GetMapToolbar().SetActiveMap(0)
  395. def ActivateSecondMap(self, event = None):
  396. """!Make second Map and MapWindow active"""
  397. self.Map = self.secondMap
  398. self.MapWindow = self.secondMapWindow
  399. self.GetMapToolbar().SetActiveMap(1)
  400. def OnZoomIn(self, event):
  401. """!Zoom in the map.
  402. Set mouse cursor, zoombox attributes, and zoom direction
  403. """
  404. toolbar = self.GetMapToolbar()
  405. self._switchTool(toolbar, event)
  406. win = self.GetFirstWindow()
  407. self._prepareZoom(mapWindow = win, zoomType = 1)
  408. win = self.GetSecondWindow()
  409. self._prepareZoom(mapWindow = win, zoomType = 1)
  410. def OnZoomOut(self, event):
  411. """!Zoom out the map.
  412. Set mouse cursor, zoombox attributes, and zoom direction
  413. """
  414. toolbar = self.GetMapToolbar()
  415. self._switchTool(toolbar, event)
  416. win = self.GetFirstWindow()
  417. self._prepareZoom(mapWindow = win, zoomType = -1)
  418. win = self.GetSecondWindow()
  419. self._prepareZoom(mapWindow = win, zoomType = -1)
  420. def OnPan(self, event):
  421. """!Panning, set mouse to drag
  422. """
  423. toolbar = self.GetMapToolbar()
  424. self._switchTool(toolbar, event)
  425. win = self.GetFirstWindow()
  426. self._preparePan(mapWindow = win)
  427. win = self.GetSecondWindow()
  428. self._preparePan(mapWindow = win)
  429. def OnPointer(self, event):
  430. self.GetFirstWindow().mouse['use'] = 'pointer'
  431. def OnRender(self, event):
  432. """!Re-render map composition (each map layer)
  433. """
  434. self.Render(mapToRender = self.GetFirstWindow())
  435. self.Render(mapToRender = self.GetSecondWindow())
  436. def Render(self, mapToRender):
  437. """!Re-render map composition"""
  438. mapToRender.UpdateMap(render = True,
  439. renderVector = mapToRender == self.GetFirstWindow())
  440. # update statusbar
  441. self.StatusbarUpdate()
  442. def OnErase(self, event):
  443. """!Erase the canvas
  444. """
  445. self.Erase(mapToErase = self.GetFirstWindow())
  446. self.Erase(mapToErase = self.GetSecondWindow())
  447. def Erase(self, mapToErase):
  448. """!Erase the canvas
  449. """
  450. mapToErase.EraseMap()
  451. def OnDraw(self, event):
  452. """!Re-display current map composition
  453. """
  454. self.Draw(mapToDraw = self.GetFirstWindow())
  455. self.Draw(mapToDraw = self.GetSecondWindow())
  456. def Draw(self, mapToDraw):
  457. """!Re-display current map composition
  458. """
  459. mapToDraw.UpdateMap(render = False)