mapdisp.py 22 KB

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