GRASS Programmer's Manual  6.4.3(2013)-r
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros Pages
nviz/mapwindow.py
Go to the documentation of this file.
1 """!
2 @package nviz.mapwindow
3 
4 @brief wxGUI 3D view mode (map canvas)
5 
6 This module implements 3D visualization mode for map display.
7 
8 List of classes:
9  - mapwindow::NvizThread
10  - mapwindow::GLWindow
11 
12 (C) 2008-2011 by the GRASS Development Team
13 
14 This program is free software under the GNU General Public License
15 (>=v2). Read the file COPYING that comes with GRASS for details.
16 
17 @author Martin Landa <landa.martin gmail.com> (Google SoC 2008/2010)
18 @author Anna Kratochvilova <kratochanna gmail.com> (Google SoC 2011)
19 """
20 
21 import os
22 import sys
23 import time
24 import copy
25 import math
26 import types
27 import tempfile
28 
29 from threading import Thread
30 
31 import wx
32 from wx.lib.newevent import NewEvent
33 from wx import glcanvas
34 from wx.glcanvas import WX_GL_DEPTH_SIZE
35 
36 import grass.script as grass
37 
38 from core.gcmd import GMessage, GException, GError
39 from core.debug import Debug
40 from gui_core.mapwindow import MapWindow
41 from gui_core.goutput import wxCmdOutput
42 from nviz.workspace import NvizSettings
43 from core.settings import UserSettings
44 from nviz.animation import Animation
45 from nviz import wxnviz
46 from core.globalvar import CheckWxVersion
47 
48 wxUpdateProperties, EVT_UPDATE_PROP = NewEvent()
49 wxUpdateView, EVT_UPDATE_VIEW = NewEvent()
50 wxUpdateLight, EVT_UPDATE_LIGHT = NewEvent()
51 wxUpdateCPlane, EVT_UPDATE_CPLANE = NewEvent()
52 
53 class NvizThread(Thread):
54  def __init__(self, log, progressbar, window):
55  Thread.__init__(self)
56  Debug.msg(5, "NvizThread.__init__():")
57  self.log = log
58  self.progressbar = progressbar
59  self.window = window
60 
61  self._display = None
62 
63  self.setDaemon(True)
64 
65  def run(self):
66  self._display = wxnviz.Nviz(self.log, self.progressbar)
67 
68  def GetDisplay(self):
69  """!Get display instance"""
70  return self._display
71 
72 class GLWindow(MapWindow, glcanvas.GLCanvas):
73  """!OpenGL canvas for Map Display Window"""
74  def __init__(self, parent, id = wx.ID_ANY,
75  Map = None, tree = None, lmgr = None):
76  self.parent = parent # MapFrame
77 
78  # for wxGTK we need to set WX_GL_DEPTH_SIZE to draw vectors correctly
79  # but we don't know the right value
80  # in wxpython 2.9, there is IsDisplaySupported
81  if CheckWxVersion(version=[2, 8, 11]) and \
82  sys.platform not in ('win32', 'darwin'):
83  depthBuffer = int(UserSettings.Get(group='display', key='nvizDepthBuffer', subkey='value'))
84  attribs=[WX_GL_DEPTH_SIZE, depthBuffer, 0]
85  glcanvas.GLCanvas.__init__(self, parent, id, attribList=attribs)
86  else:
87  glcanvas.GLCanvas.__init__(self, parent, id)
88 
89 
90  MapWindow.__init__(self, parent, id,
91  Map, tree, lmgr)
92  self.Hide()
93 
94  self.init = False
95  self.initView = False
96 
97  # render mode
98  self.render = { 'quick' : False,
99  # do not render vector lines in quick mode
100  'vlines' : False,
101  'vpoints' : False,
102  'overlays': False }
103  self.mouse = {
104  'use': 'pointer'
105  }
106  self.cursors = {
107  'default' : wx.StockCursor(wx.CURSOR_ARROW),
108  'cross' : wx.StockCursor(wx.CURSOR_CROSS),
109  }
110  # list of loaded map layers (layer tree items)
111  self.layers = list()
112  # list of constant surfaces
113  self.constants = list()
114  # id of base surface (when vector is loaded and no surface exist)
115  self.baseId = -1
116  # list of cutting planes
117  self.cplanes = list()
118  # list of query points
119  self.qpoints = list()
120  # list of past views
121  self.viewhistory = []
122  self.saveHistory = False
123  # offset for dialog (e.g. DisplayAttributesDialog)
124  self.dialogOffset = 5
125  # overlays
126  self.overlays = {}
127  self.imagelist = []
128  self.overlay = wx.Overlay()
129  #self.pdc = wx.PseudoDC()
130  self.textdict = {}
131  self.dragid = -1
132  self.hitradius = 5
133  # layer manager toolwindow
134  self.toolWin = None
135 
136  if self.lmgr:
137  self.log = self.lmgr.goutput
138  logerr = self.lmgr.goutput.GetLog(err = True)
139  logmsg = self.lmgr.goutput.GetLog()
140  else:
141  self.log = logmsg = sys.stdout
142  logerr = sys.stderr
143 
144  # create nviz instance - use display region instead of computational
145  os.environ['GRASS_REGION'] = self.Map.SetRegion(windres = True)
146 
147  self.nvizThread = NvizThread(logerr,
148  self.parent.GetProgressBar(),
149  logmsg)
150  self.nvizThread.start()
151  time.sleep(.1)
152  self._display = self.nvizThread.GetDisplay()
153 
154  # GRASS_REGION needed only for initialization
155  del os.environ['GRASS_REGION']
156 
157  self.img = wx.Image(self.Map.mapfile, wx.BITMAP_TYPE_ANY)
158 
159  # size of MapWindow, to avoid resizing if size is the same
160  self.size = (0, 0)
161 
162  # default values
164  self.view = copy.deepcopy(UserSettings.Get(group = 'nviz', key = 'view')) # copy
165  self.iview = UserSettings.Get(group = 'nviz', key = 'view', internal = True)
166  self.light = copy.deepcopy(UserSettings.Get(group = 'nviz', key = 'light')) # copy
167  self.decoration = self.nvizDefault.SetDecorDefaultProp(type = 'arrow')
168  self.decoration['scalebar'] = []
169  self.decoration['arrow']['size'] = self._getDecorationSize()
170  self.fly = self.InitFly()
171 
172  # timer for flythrough
173  self.timerFly = wx.Timer(self, id = wx.NewId())
174  # timer for animations
175  self.timerAnim = wx.Timer(self, id = wx.NewId())
176  self.animation = Animation(mapWindow = self, timer = self.timerAnim)
177 
178  self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
179  self.Bind(wx.EVT_SIZE, self.OnSize)
180  self.Bind(wx.EVT_PAINT, self.OnPaint)
181  self._bindMouseEvents()
182 
183  self.Bind(EVT_UPDATE_PROP, self.UpdateMapObjProperties)
184  self.Bind(EVT_UPDATE_VIEW, self.OnUpdateView)
185  self.Bind(EVT_UPDATE_LIGHT, self.UpdateLight)
186  self.Bind(EVT_UPDATE_CPLANE, self.OnUpdateCPlane)
187 
188  self.Bind(wx.EVT_TIMER, self.OnTimerAnim, self.timerAnim)
189  self.Bind(wx.EVT_TIMER, self.OnTimerFly, self.timerFly)
190  self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
191  self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
192 
193  self.Bind(wx.EVT_CLOSE, self.OnClose)
194 
195  if CheckWxVersion(version=[2, 8, 11]) and \
196  sys.platform not in ('win32', 'darwin'):
197  wx.CallLater(3000, self._warningDepthBuffer)
198 
199  # cplanes cannot be initialized now
200  wx.CallAfter(self.InitCPlanes)
201 
202  def _warningDepthBuffer(self):
203  if not self.initView:
204  message=_("Opening 3D view was not successful. "
205  "Please try to change the value of depth buffer "
206  "in GUI Settings dialog > tab Map Display > Advanced "
207  "and restart GUI.")
208  GMessage(message)
209 
210  def InitFly(self):
211  """!Initialize fly through dictionary"""
212  fly = {'interval' : 10, # interval for timerFly
213  'value': [0, 0, 0], # calculated values for navigation
214  'mode' : 0, # fly through mode (0, 1)
215  'exag' : { # sensitivity
216  'move' : UserSettings.Get(group = 'nviz', key = 'fly', subkey = ['exag', 'move']),
217  'turn' : UserSettings.Get(group = 'nviz', key = 'fly', subkey = ['exag', 'turn'])},
218  'exagMultiplier' : 3, # speed up by Shift
219  'flySpeed' : 4, # speed of flying
220  'mouseControl' : None, # if mouse or keys are used
221  'pos' : {'x' : 0, 'y' : 0}, # virtual mouse position when using arrows
222  'arrowStep' : 50, # step in pixels (when using arrows)
223  'flySpeedStep' : 2,
224  }
225 
226  return fly
227 
228  def OnTimerFly(self, event):
229  """!Fly event was emitted, move the scene"""
230  if self.mouse['use'] != 'fly':
231  return
232 
233  if self.fly['mouseControl']:
234  mx, my = self.ComputeMxMy(*self.mouse['tmp'])
235  else:
236  mx, my = self.ComputeMxMy(self.fly['pos']['x'], self.fly['pos']['y'])
237 
238  self.ComputeFlyValues(mx = mx, my = my)
239  self._display.FlyThrough(flyInfo = self.fly['value'], mode = self.fly['mode'],
240  exagInfo = self.fly['exag'])
241  self.ChangeInnerView()
242  self.render['quick'] = True
243  self.Refresh(False)
244 
245  def ComputeMxMy(self, x, y):
246  """!Compute values for flythrough navigation
247  (ComputeFlyValues should follow).
248 
249  Based on visualization/nviz/src/togl_flythrough.c.
250  @param x,y screen coordinates
251  """
252  sx, sy = self.GetClientSizeTuple()
253  dx = dy = 0.01
254 
255  mx = 2 * (float(x) / sx) - 1
256  my = 2 * (float(y) / sy) - 1
257 
258  if mx < - dx:
259  mx += dx
260  elif mx > dx:
261  mx -= dx
262  else:
263  mx = 0.0 # ?
264  if my < - dy:
265  my += dy
266  elif my > dy:
267  my -= dy
268  else:
269  my = 0.0
270 
271  mx = mx / (1.0 - dx)
272  my = my / (1.0 - dy)
273 
274  # Quadratic seems smoother
275  mx *= abs(mx)
276  my *= abs(my)
277 
278  return mx, my
279 
280  def ComputeFlyValues(self, mx, my):
281  """!Compute parameters for fly-through navigation
282 
283  @params mx,my results from ComputeMxMy method
284  """
285  self.fly['value'] = [0, 0, 0]
286 
287  if self.fly['mode'] == 0:
288  self.fly['value'][0] = self.fly['flySpeed'] * self.fly['interval'] / 1000. # forward */
289  self.fly['value'][1] = mx * 0.1 * self.fly['interval'] / 1000. # heading
290  self.fly['value'][2] = my * 0.1 * self.fly['interval'] / 1000. # pitch
291  else:
292  self.fly['value'][0] = mx * 100.0 * self.fly['interval'] /1000.
293  self.fly['value'][2] = - my * 100.0 * self.fly['interval'] /1000.
294 
295  def ChangeFlySpeed(self, increase):
296  """!Increase/decrease flight spped"""
297  if increase:
298  self.fly['flySpeed'] += self.fly['flySpeedStep']
299  else:
300  self.fly['flySpeed'] -= self.fly['flySpeedStep']
301 
302  def __del__(self):
303  """!Stop timers if running, unload data"""
304  self.StopTimer(self.timerAnim)
305  self.StopTimer(self.timerFly)
306  self.UnloadDataLayers(force = True)
307 
308  def StopTimer(self, timer):
309  """!Stop timer if running"""
310  if timer.IsRunning():
311  timer.Stop()
312 
313  def _bindMouseEvents(self):
314  self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouseAction)
315  self.Bind(wx.EVT_MOTION, self.OnMotion)
316 
317  def InitCPlanes(self):
318  """!Initialize cutting planes list"""
319  for i in range(self._display.GetCPlanesCount()):
320  cplane = copy.deepcopy(UserSettings.Get(group = 'nviz', key = 'cplane'))
321  cplane['on'] = False
322  self.cplanes.append(cplane)
323 
324  def SetToolWin(self, toolWin):
325  """!Sets reference to nviz toolwindow in layer manager"""
326  self.toolWin = toolWin
327 
328  def GetToolWin(self):
329  """!Returns reference to nviz toolwindow in layer manager"""
330  return self.toolWin
331 
332  def OnClose(self, event):
333  self.StopTimer(self.timerAnim)
334  self.StopTimer(self.timerFly)
335  # cleanup when window actually closes (on quit) and not just is hidden
336  self.UnloadDataLayers(force = True)
337 
338  def OnEraseBackground(self, event):
339  pass # do nothing, to avoid flashing on MSW
340 
341  def OnSize(self, event):
342  size = self.GetClientSize()
343  if self.size != size \
344  and self.GetContext():
345  Debug.msg(3, "GLCanvas.OnSize(): w = %d, h = %d" % \
346  (size.width, size.height))
347  self.SetCurrent()
348  self._display.ResizeWindow(size.width,
349  size.height)
350 
351  # reposition checkbox in statusbar
352  self.parent.StatusbarReposition()
353 
354  # update statusbar
355  self.parent.StatusbarUpdate()
356 
357  self.size = size
358 
359  event.Skip()
360 
361  def OnPaint(self, event):
362  Debug.msg(1, "GLCanvas.OnPaint()")
363 
364  self.render['overlays'] = True
365  dc = wx.PaintDC(self)
366  self.DoPaint()
367 
368 
369  def DoPaint(self):
370  self.SetCurrent()
371 
372  if not self.initView:
373  self._display.InitView()
374  self.initView = True
375 
376  self.LoadDataLayers()
377  self.UnloadDataLayers()
378 
379  if not self.init:
380  self.ResetView()
381 
382  if hasattr(self.lmgr, "nviz"):
383  self.lmgr.nviz.UpdatePage('view')
384  self.lmgr.nviz.UpdatePage('light')
385  self.lmgr.nviz.UpdatePage('cplane')
386  self.lmgr.nviz.UpdatePage('decoration')
387  self.lmgr.nviz.UpdatePage('animation')
388  layer = self.GetSelectedLayer()
389  if layer:
390  if layer.type == 'raster':
391  self.lmgr.nviz.UpdatePage('surface')
392  self.lmgr.nviz.UpdatePage('fringe')
393  elif layer.type == 'vector':
394  self.lmgr.nviz.UpdatePage('vector')
395 
396  self.lmgr.nviz.UpdateSettings()
397 
398  # update widgets
399  win = self.lmgr.nviz.FindWindowById( \
400  self.lmgr.nviz.win['vector']['lines']['surface'])
401  win.SetItems(self.GetLayerNames('raster'))
402 
403  self.init = True
404 
405  self.UpdateMap()
406 
407  def DrawImages(self):
408  """!Draw overlay image"""
409  for texture in self.imagelist:
410  if texture.IsActive():
411  texture.Draw()
412 
413  def GetLegendRect(self):
414  """!Estimates legend size for dragging"""
415  size = None
416  if 1 in self.overlays:
417  for param in self.overlays[1].cmd[1:]:
418  if param.startswith("at="):
419  size = map(int, param.split("=")[-1].split(','))
420  break
421  if size:
422  wSize = self.GetClientSizeTuple()
423  x, y = size[2]/100. * wSize[0], wSize[1] - (size[1]/100. * wSize[1])
424  x += self.overlays[1].coords[0]
425  y += self.overlays[1].coords[1]
426  w = (size[3] - size[2])/100. * wSize[0]
427  h = (size[1] - size[0])/100. * wSize[1]
428 
429  rect = wx.Rect(x, y, w, h)
430  return rect
431 
432  return wx.Rect()
433 
434  def DrawTextImage(self, textDict, relCoords):
435  """!Draw overlay text"""
436  bmp = wx.EmptyBitmap(textDict['bbox'][2], textDict['bbox'][3])
437  memDC = wx.MemoryDC()
438  memDC.SelectObject(bmp)
439 
440  mask = self.view['background']['color']
441  if mask == textDict['color']:
442  mask = wx.WHITE
443  memDC.SetBackground(wx.Brush(mask))
444  memDC.Clear()
445  memDC.SetFont(textDict['font'])
446  memDC.SetTextForeground(textDict['color'])
447  if textDict['rotation'] == 0:
448  memDC.DrawText(textDict['text'], 0, 0)
449  else:
450  memDC.DrawRotatedText(textDict['text'], relCoords[0], relCoords[1],
451  textDict['rotation'])
452  bmp.SetMaskColour(mask)
453  memDC.DrawBitmap(bmp, 0, 0, 1)
454 
455  filename = tempfile.mktemp() + '.png'
456  bmp.SaveFile(filename, wx.BITMAP_TYPE_PNG)
457  memDC.SelectObject(wx.NullBitmap)
458 
459  return filename
460 
461  def UpdateOverlays(self):
462  """!Converts rendered overlay files and text labels to wx.Image
463  and then to textures so that they can be rendered by OpenGL.
464  Updates self.imagelist"""
465  self.Map.ChangeMapSize(self.GetClientSize())
466  self.Map.RenderOverlays(force = True)
467 
468  # delete textures
469  for texture in self.imagelist:
470  # inactive overlays, remove text labels
471  if texture.GetId() < 100:
472  if not self.overlays[texture.GetId()].IsShown():
473  texture.SetActive(False)
474  else:
475  texture.SetActive(True)
476  else: # text label
477  if texture.GetId() not in self.textdict:
478  self.imagelist.remove(texture)
479 
480  # update images (only legend so far)
481  for oid, overlay in self.overlays.iteritems():
482  if not overlay.IsShown() or oid == 0: # 0 for barscale
483  continue
484  if oid not in [t.GetId() for t in self.imagelist]: # new
485  self.CreateTexture(overlay = overlay.layer)
486  else:
487  for t in self.imagelist:
488  if t.GetId() == oid: # check if it is the same
489  if not t.Corresponds(overlay):
490  self.imagelist.remove(t)
491  t = self.CreateTexture(overlay = overlay.layer)
492  # always set coordinates, needed for synchr. 2D and 3D modes
493  t.SetCoords(overlay.coords)
494 
495 
496  # update text labels
497  for textId in self.textdict.keys():
498  if textId not in [t.GetId() for t in self.imagelist]:# new
499  self.CreateTexture(textId = textId)
500  else:
501  for t in self.imagelist:
502  if t.GetId() == textId: # check if it is the same
503  self.textdict[textId]['bbox'] = t.textDict['bbox']
504  if not t.Corresponds(self.textdict[textId]):
505  self.imagelist.remove(t)
506  t = self.CreateTexture(textId = textId)
507  # always set coordinates, needed for synchr. 2D and 3D modes
508  t.SetCoords(self.textdict[textId]['coords'])
509 
510  def CreateTexture(self, overlay = None, textId = None):
511  """!Create texture from overlay image or from textdict"""
512  if overlay: # legend
513  texture = wxnviz.ImageTexture(filepath = overlay.mapfile, overlayId = overlay.id,
514  coords = list(self.overlays[overlay.id].coords),
515  cmd = overlay.GetCmd())
516  if overlay.id == 1: # legend
517  texture.SetBounds(self.GetLegendRect())
518  else: # text
519  coords, bbox, relCoords = self.TextBounds(self.textdict[textId])
520  self.textdict[textId]['coords'] = coords
521  self.textdict[textId]['bbox'] = bbox
522  file = self.DrawTextImage(self.textdict[textId], relCoords)
523  texture = wxnviz.TextTexture(filepath = file, overlayId = textId,
524  coords = coords, textDict = self.textdict[textId])
525  bbox.OffsetXY(*relCoords)
526  texture.SetBounds(bbox)
527 
528  if not texture.textureId: # texture too big
529  GMessage(parent = self, message =
530  _("Image is too large, your OpenGL implementation "
531  "supports maximum texture size %d px.") % texture.maxSize)
532  return texture
533 
534  self.imagelist.append(texture)
535 
536  return texture
537 
538  def FindObjects(self, mouseX, mouseY, radius):
539  """Find object which was clicked on"""
540  for texture in self.imagelist:
541  if texture.HitTest(mouseX, mouseY, radius):
542  return texture.id
543  return -1
544 
545  def OnTimerAnim(self, event):
546  self.animation.Update()
547 
548  def GetAnimation(self):
549  return self.animation
550 
551  def OnKeyDown(self, event):
552  """!Key was pressed.
553 
554  Used for fly-through mode.
555  """
556  if not self.mouse['use'] == 'fly':
557  return
558 
559  key = event.GetKeyCode()
560  if key == wx.WXK_CONTROL: # Mac ?
561  self.fly['mode'] = 1
562 
563  elif key == wx.WXK_SHIFT:
564  self.fly['exag']['move'] *= self.fly['exagMultiplier']
565  self.fly['exag']['turn'] *= self.fly['exagMultiplier']
566 
567  elif key == wx.WXK_ESCAPE and self.timerFly.IsRunning() and not self.fly['mouseControl']:
568  self.StopTimer(self.timerFly)
569  self.fly['mouseControl'] = None
570  self.render['quick'] = False
571  self.Refresh(False)
572 
573  elif key in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT, wx.WXK_RIGHT):
574  if not self.fly['mouseControl']:
575  if not self.timerFly.IsRunning():
576  sx, sy = self.GetClientSizeTuple()
577  self.fly['pos']['x'] = sx / 2
578  self.fly['pos']['y'] = sy / 2
579  self.fly['mouseControl'] = False # controlled by keyboard
580  self.timerFly.Start(self.fly['interval'])
581 
582  self.ProcessFlyByArrows(keyCode = key)
583 
584  # change speed of flight when using mouse
585  else:
586  if key == wx.WXK_UP:
587  self.ChangeFlySpeed(increase = True)
588  elif key == wx.WXK_DOWN:
589  self.ChangeFlySpeed(increase = False)
590 
591  elif key in (wx.WXK_HOME, wx.WXK_PAGEUP) and self.timerFly.IsRunning():
592  self.ChangeFlySpeed(increase = True)
593  elif key in (wx.WXK_END, wx.WXK_PAGEDOWN) and self.timerFly.IsRunning():
594  self.ChangeFlySpeed(increase = False)
595 
596  event.Skip()
597 
598  def ProcessFlyByArrows(self, keyCode):
599  """!Process arrow key during fly-through"""
600  step = self.fly['arrowStep']
601  if keyCode == wx.WXK_UP:
602  self.fly['pos']['y'] -= step
603  elif keyCode == wx.WXK_DOWN:
604  self.fly['pos']['y'] += step
605  elif keyCode == wx.WXK_LEFT:
606  self.fly['pos']['x'] -= step
607  elif keyCode == wx.WXK_RIGHT:
608  self.fly['pos']['x'] += step
609 
610  def OnKeyUp(self, event):
611  """!Key was released.
612 
613  Used for fly-through mode.
614  """
615  if not self.mouse['use'] == 'fly':
616  return
617 
618  key = event.GetKeyCode()
619  if key == wx.WXK_CONTROL: # Mac ?
620  self.fly['mode'] = 0
621  elif key == wx.WXK_SHIFT:
622  self.fly['exag']['move'] = math.floor(self.fly['exag']['move'] / self.fly['exagMultiplier'])
623  self.fly['exag']['turn'] = math.floor(self.fly['exag']['turn'] / self.fly['exagMultiplier'])
624 
625  event.Skip()
626 
627  def OnMouseAction(self, event):
628  """!Handle mouse events"""
629  # zoom with mouse wheel
630  if event.GetWheelRotation() != 0:
631  self.OnMouseWheel(event)
632 
633  # left mouse button pressed
634  elif event.LeftDown():
635  self.OnLeftDown(event)
636 
637  # left mouse button released
638  elif event.LeftUp():
639  self.OnLeftUp(event)
640 
641  # dragging
642  elif event.Dragging():
643  self.OnDragging(event)
644 
645  # double click
646  elif event.ButtonDClick():
647  self.OnDClick(event)
648 
649  event.Skip()
650 
651  def OnMouseWheel(self, event):
652  """!Change perspective"""
653  if UserSettings.Get(group = 'display',
654  key = 'mouseWheelZoom',
655  subkey = 'selection') == 2:
656  event.Skip()
657  return
658 
659  wheel = event.GetWheelRotation()
660  Debug.msg (5, "GLWindow.OnMouseMotion(): wheel = %d" % wheel)
661  if self.timerFly.IsRunning() and self.fly['mouseControl']:
662  if wheel > 0:
663  self.ChangeFlySpeed(increase = True)
664  else:
665  self.ChangeFlySpeed(increase = False)
666  else:
667  if UserSettings.Get(group = 'display',
668  key = 'scrollDirection',
669  subkey = 'selection'):
670  wheel *= -1
671  self.DoZoom(zoomtype = wheel, pos = event.GetPositionTuple())
672 
673  # update statusbar
674  ### self.parent.StatusbarUpdate()
675 
676  def OnLeftDown(self, event):
677  """!On left mouse down"""
678  self.mouse['begin'] = event.GetPositionTuple()
679  self.mouse['tmp'] = event.GetPositionTuple()
680  if self.mouse['use'] == "lookHere":
681  size = self.GetClientSize()
682  self._display.LookHere(self.mouse['begin'][0], size[1] - self.mouse['begin'][1])
683  focus = self._display.GetFocus()
684  for i, coord in enumerate(('x', 'y', 'z')):
685  self.iview['focus'][coord] = focus[i]
686  self.saveHistory = True
687  self.Refresh(False)
688  toggle = self.lmgr.nviz.FindWindowByName('here')
689  toggle.SetValue(False)
690  self.mouse['use'] = 'pointer'
691  self.SetCursor(self.cursors['default'])
692 
693  if self.mouse['use'] == 'arrow':
694  pos = event.GetPosition()
695  size = self.GetClientSize()
696  self.SetDrawArrow((pos[0], size[1] - pos[1]))
697 
698  if self.mouse['use'] == 'scalebar':
699  pos = event.GetPosition()
700  size = self.GetClientSize()
701  self.SetDrawScalebar((pos[0], size[1] - pos[1]))
702 
703  if self.mouse['use'] == 'pointer':
704  # get decoration or text id
705  self.dragid = self.FindObjects(self.mouse['tmp'][0], self.mouse['tmp'][1],
706  self.hitradius)
707 
708  if self.mouse['use'] == 'fly':
709  if not self.timerFly.IsRunning():
710  self.timerFly.Start(self.fly['interval'])
711  self.fly['mouseControl'] = True
712 
713  event.Skip()
714 
715  def OnDragging(self, event):
716  if self.mouse['use'] == 'pointer':
717  if self.dragid > 0:
718 
719  self.DragItem(self.dragid, event)
720 
721  if self.mouse['use'] == 'rotate':
722  dx, dy = event.GetX() - self.mouse['tmp'][0], event.GetY() - self.mouse['tmp'][1]
723 
724  angle, x, y, z = self._display.GetRotationParameters(dx, dy)
725  self._display.Rotate(angle, x, y, z)
726 
727  self.render['quick'] = True
728  self.Refresh(False)
729 
730  if self.mouse['use'] == 'pan':
731  self.FocusPanning(event)
732 
733  self.mouse['tmp'] = event.GetPositionTuple()
734 
735  event.Skip()
736 
737  def Pixel2Cell(self, (x, y)):
738  """!Convert image coordinates to real word coordinates
739 
740  @param x, y image coordinates
741 
742  @return easting, northing
743  @return None on error
744  """
745  size = self.GetClientSize()
746  # UL -> LL
747  sid, x, y, z = self._display.GetPointOnSurface(x, size[1] - y)
748 
749  if not sid:
750  return None
751 
752  return (x, y)
753 
754  def DoZoom(self, zoomtype, pos):
755  """!Change perspective and focus"""
756 
757  prev_value = self.view['persp']['value']
758  if zoomtype > 0:
759  value = -1 * self.view['persp']['step']
760  else:
761  value = self.view['persp']['step']
762  self.view['persp']['value'] += value
763  if self.view['persp']['value'] < 1:
764  self.view['persp']['value'] = 1
765  elif self.view['persp']['value'] > 180:
766  self.view['persp']['value'] = 180
767 
768  if prev_value != self.view['persp']['value']:
769  if hasattr(self.lmgr, "nviz"):
770  self.lmgr.nviz.UpdateSettings()
771  x, y = pos[0], self.GetClientSize()[1] - pos[1]
772  result = self._display.GetPointOnSurface(x, y)
773  if result[0]:
774  self._display.LookHere(x, y)
775  focus = self._display.GetFocus()
776  for i, coord in enumerate(('x', 'y', 'z')):
777  self.iview['focus'][coord] = focus[i]
778  self._display.SetView(self.view['position']['x'], self.view['position']['y'],
779  self.iview['height']['value'],
780  self.view['persp']['value'],
781  self.view['twist']['value'])
782  self.saveHistory = True
783  # redraw map
784  self.DoPaint()
785 
786  def OnLeftUp(self, event):
787  self.mouse['end'] = event.GetPositionTuple()
788  if self.mouse["use"] == "query":
789  # querying
790  if self.parent.IsStandalone():
791  GMessage(parent = self.parent,
792  message = _("Querying is not implemented in standalone mode of Map Display"))
793  return
794 
795  layers = self.GetSelectedLayer(type = 'item', multi = True)
796 
797  self.parent.Query(self.mouse['begin'][0],self.mouse['begin'][1], layers)
798 
799  elif self.mouse["use"] in ('arrow', 'scalebar'):
800  self.lmgr.nviz.FindWindowById(
801  self.lmgr.nviz.win['decoration'][self.mouse["use"]]['place']).SetValue(False)
802  self.mouse['use'] = 'pointer'
803  self.SetCursor(self.cursors['default'])
804  elif self.mouse['use'] == 'pointer':
805  if self.dragid > 0:
806  dx = self.mouse['end'][0] - self.mouse['begin'][0]
807  dy = self.mouse['end'][1] - self.mouse['begin'][1]
808  if self.dragid < 99:
809  coords = self.overlays[self.dragid].coords
810  self.overlays[self.dragid].coords = [coords[0] + dx, coords[1] + dy]
811  else: # text
812  coords = self.textdict[self.dragid]['coords']
813  self.textdict[self.dragid]['coords'] = [coords[0] + dx, coords[1] + dy]
814  self.dragid = -1
815  self.render['quick'] = False
816  self.Refresh(False)
817 
818  elif self.mouse['use'] == 'rotate':
819  self._display.UnsetRotation()
820  self.iview['rotation'] = self._display.GetRotationMatrix()
821  self.saveHistory = True
822  self.render['quick'] = False
823  self.Refresh(False)
824 
825  elif self.mouse['use'] == 'pan':
826  self.saveHistory = True
827  self.render['quick'] = False
828  self.Refresh(False)
829 
830  elif self.mouse['use'] == 'fly':
831  if self.fly['mouseControl']:
832  self.StopTimer(self.timerFly)
833  self.fly['mouseControl'] = None
834  #for key in self.iview['dir'].keys():
835  #self.iview[''][key] = -1
836  # this causes sudden change, but it should be there
837  #if hasattr(self.lmgr, "nviz"):
838  #self.lmgr.nviz.UpdateSettings()
839 
840  self.render['quick'] = False
841  self.Refresh(False)
842 
843  elif self.mouse['use'] == 'zoom':
844  self.DoZoom(zoomtype = self.zoomtype, pos = self.mouse['end'])
845  event.Skip()
846 
847  def OnDClick(self, event):
848  """!On mouse double click"""
849  if self.mouse['use'] != 'pointer': return
850  pos = event.GetPositionTuple()
851  self.dragid = self.FindObjects(pos[0], pos[1], self.hitradius)
852 
853  if self.dragid == 1:
854  self.parent.AddLegend()
855  elif self.dragid > 100:
856  self.parent.OnAddText(None)
857  else:
858  return
859 
860  def FocusPanning(self, event):
861  """!Simulation of panning using focus"""
862  size = self.GetClientSizeTuple()
863  id1, x1, y1, z1 = self._display.GetPointOnSurface(
864  self.mouse['tmp'][0], size[1] - self.mouse['tmp'][1])
865  id2, x2, y2, z2 = self._display.GetPointOnSurface(
866  event.GetX(), size[1] - event.GetY())
867  if id1 and id1 == id2:
868  dx, dy, dz = x2 - x1, y2 - y1, z2 - z1
869  focus = self.iview['focus']
870  focus['x'], focus['y'], focus['z'] = self._display.GetFocus()
871  focus['x'] -= dx
872  focus['y'] -= dy
873  focus['z'] -= dz
874 
875  #update properties
876  self.PostViewEvent()
877 
878  self.mouse['tmp'] = event.GetPositionTuple()
879  self.render['quick'] = True
880  self.Refresh(False)
881 
882  def HorizontalPanning(self, event):
883  """!Move all layers in horizontal (x, y) direction.
884  Currently not used.
885  """
886  size = self.GetClientSizeTuple()
887  id1, x1, y1, z1 = self._display.GetPointOnSurface(
888  self.mouse['tmp'][0], size[1] - self.mouse['tmp'][1])
889  id2, x2, y2, z2 = self._display.GetPointOnSurface(
890  event.GetX(), size[1] - event.GetY())
891 
892  if id1 and id1 == id2:
893  dx, dy = x2 - x1, y2 - y1
894  # find raster and volume
895  for item in self.layers:
896  mapLayer = self.tree.GetPyData(item)[0]['maplayer']
897 
898  data = self.tree.GetPyData(item)[0]['nviz']
899  if mapLayer.GetType() == 'raster':
900  data['surface']['position']['x'] += dx
901  data['surface']['position']['y'] += dy
902  data['surface']['position']['update'] = None
903 
904  #update properties
905  evt = wxUpdateProperties(data = data)
906  wx.PostEvent(self, evt)
907 
908  if event.CmdDown() and id1 == data['surface']['object']['id']:
909  break
910 
911  elif mapLayer.GetType() == '3d-raster':
912  if 'x' not in data['volume']['position']:
913  data['volume']['position']['x'] = 0
914  data['volume']['position']['y'] = 0
915  data['volume']['position']['z'] = 0
916  data['volume']['position']['x'] += dx
917  data['volume']['position']['y'] += dy
918  data['volume']['position']['update'] = None
919 
920  #update properties
921  evt = wxUpdateProperties(data = data)
922  wx.PostEvent(self, evt)
923 
924  self.mouse['tmp'] = event.GetPositionTuple()
925  self.render['quick'] = True
926  self.Refresh(False)
927 
928  def DragItem(self, id, event):
929  """!Drag an overlay decoration item
930  """
931  if not id: return
932  Debug.msg (5, "GLWindow.DragItem(): id=%d" % id)
933  x, y = self.mouse['tmp']
934  dx = event.GetX() - x
935  dy = event.GetY() - y
936  for texture in self.imagelist:
937  if texture.id == id:
938  texture.MoveTexture(dx, dy)
939 
940 
941  self.render['quick'] = True
942  self.Refresh(False)
943 
944  self.mouse['tmp'] = (event.GetX(), event.GetY())
945 
946  def ZoomBack(self):
947  """!Set previous view in history list
948  """
949  view = {}
950  if len(self.viewhistory) > 1:
951  self.viewhistory.pop()
952  view = copy.deepcopy(self.viewhistory[-1])
953 
954  # disable tool if stack is empty
955  if len(self.viewhistory) < 2: # disable tool
956  toolbar = self.parent.GetMapToolbar()
957  toolbar.Enable('zoomback', enable = False)
958 
959  # set view and update nviz view page
960  self.lmgr.nviz.UpdateState(view = view[0], iview = view[1])
961  self.lmgr.nviz.UpdatePage('view')
962  # update map
963  self.Refresh(False)
964 
965  def ViewHistory(self, view, iview):
966  """!Manages a list of last 10 views
967 
968  @param view view dictionary
969  @param iview view dictionary (internal)
970 
971  @return removed history item if exists (or None)
972  """
973  removed = None
974  hview = copy.deepcopy(view)
975  hiview = copy.deepcopy(iview)
976 
977  if not (self.viewhistory and self.viewhistory[-1] == (hview, hiview)):
978  self.viewhistory.append((hview, hiview))
979 
980  if len(self.viewhistory) > 10:
981  removed = self.viewhistory.pop(0)
982 
983  if removed:
984  Debug.msg(4, "GLWindow.ViewHistory(): hist=%s, removed=%s" %
985  (self.viewhistory, removed))
986  else:
987  Debug.msg(4, "GLWindow.ViewHistory(): hist=%s" %
988  (self.viewhistory))
989 
990  # update toolbar
991  if len(self.viewhistory) > 1:
992  enable = True
993  else:
994  enable = False
995 
996  toolbar = self.parent.GetMapToolbar()
997  toolbar.Enable('zoomback', enable)
998 
999  return removed
1000 
1001  def ResetViewHistory(self):
1002  """!Reset view history"""
1003  self.viewhistory = list()
1004 
1005  def GoTo(self, e, n):
1006  """!Focus on given point"""
1007  w = self.Map.region['w']
1008  s = self.Map.region['s']
1009  e -= w
1010  n -= s
1011  focus = self.iview['focus']
1012  focus['x'], focus['y'] = e, n
1013  self.saveHistory = True
1014  #update properties
1015  self.PostViewEvent()
1016 
1017  self.render['quick'] = False
1018  self.Refresh(False)
1019 
1020  def QuerySurface(self, x, y):
1021  """!Query surface on given position"""
1022  size = self.GetClientSizeTuple()
1023  result = self._display.QueryMap(x, size[1] - y)
1024  if result:
1025  self.qpoints.append((result['x'], result['y'], result['z']))
1026  self.log.WriteLog("%-30s: %.3f" % (_("Easting"), result['x']))
1027  self.log.WriteLog("%-30s: %.3f" % (_("Northing"), result['y']))
1028  self.log.WriteLog("%-30s: %.3f" % (_("Elevation"), result['z']))
1029  name = ''
1030  for item in self.layers:
1031  self.tree.GetPyData(item)[0]['nviz']
1032  if self.tree.GetPyData(item)[0]['maplayer'].type == 'raster' and\
1033  self.tree.GetPyData(item)[0]['nviz']['surface']['object']['id'] == result['id']:
1034  name = self.tree.GetPyData(item)[0]['maplayer'].name
1035  self.log.WriteLog("%-30s: %s" % (_("Surface map name"), name))
1036  self.log.WriteLog("%-30s: %s" % (_("Surface map elevation"), result['elevation']))
1037  self.log.WriteLog("%-30s: %s" % (_("Surface map color"), result['color']))
1038  if len(self.qpoints) > 1:
1039  prev = self.qpoints[-2]
1040  curr = self.qpoints[-1]
1041  dxy = math.sqrt(pow(prev[0]-curr[0], 2) +
1042  pow(prev[1]-curr[1], 2))
1043  dxyz = math.sqrt(pow(prev[0]-curr[0], 2) +
1044  pow(prev[1]-curr[1], 2) +
1045  pow(prev[2]-curr[2], 2))
1046  self.log.WriteLog("%-30s: %.3f" % (_("XY distance from previous"), dxy))
1047  self.log.WriteLog("%-30s: %.3f" % (_("XYZ distance from previous"), dxyz))
1048  self.log.WriteLog("%-30s: %.3f" % (_("Distance along surface"),
1049  self._display.GetDistanceAlongSurface(result['id'],
1050  (curr[0], curr[1]),
1051  (prev[0], prev[1]),
1052  useExag = False)))
1053  self.log.WriteLog("%-30s: %.3f" % (_("Distance along exag. surface"),
1054  self._display.GetDistanceAlongSurface(result['id'],
1055  (curr[0], curr[1]),
1056  (prev[0], prev[1]),
1057  useExag = True)))
1058  self.log.WriteCmdLog('-' * 80)
1059  else:
1060  self.log.WriteLog(_("No point on surface"))
1061  self.log.WriteCmdLog('-' * 80)
1062 
1063  def PostViewEvent(self, zExag = False):
1064  """!Change view settings"""
1065  event = wxUpdateView(zExag = zExag)
1066  wx.PostEvent(self, event)
1067 
1068  def OnQueryVector(self, event):
1069  """!Query vector on given position"""
1070  self.parent.QueryVector(*event.GetPosition())
1071 
1072  def ChangeInnerView(self):
1073  """!Get current viewdir and viewpoint and set view"""
1074  view = self.view
1075  iview = self.iview
1076  (view['position']['x'], view['position']['y'],
1077  iview['height']['value']) = self._display.GetViewpointPosition()
1078  for key, val in zip(('x', 'y', 'z'), self._display.GetViewdir()):
1079  iview['dir'][key] = val
1080 
1081  iview['dir']['use'] = True
1082 
1083  def OnUpdateView(self, event):
1084  """!Change view settings"""
1085  if event:
1086  self.UpdateView(zexag = event.zExag)
1087 
1088  self.saveHistory = True
1089  if event:
1090  event.Skip()
1091 
1092 
1093  def UpdateView(self, zexag = False):
1094  """!Change view settings"""
1095  view = self.view
1096  iview = self.iview
1097  if zexag and 'value' in view['z-exag']:
1098  self._display.SetZExag(view['z-exag']['value'] / iview['z-exag']['llRatio'])
1099 
1100  self._display.SetView(view['position']['x'], view['position']['y'],
1101  iview['height']['value'],
1102  view['persp']['value'],
1103  view['twist']['value'])
1104 
1105  if iview['dir']['use']:
1106  self._display.SetViewdir(iview['dir']['x'], iview['dir']['y'], iview['dir']['z'])
1107 
1108  elif iview['focus']['x'] != -1:
1109  self._display.SetFocus(self.iview['focus']['x'], self.iview['focus']['y'],
1110  self.iview['focus']['z'])
1111 
1112  if 'rotation' in iview:
1113  if iview['rotation']:
1114  self._display.SetRotationMatrix(iview['rotation'])
1115  else:
1116  self._display.ResetRotation()
1117 
1118  def UpdateLight(self, event):
1119  """!Change light settings"""
1120  data = self.light
1121  self._display.SetLight(x = data['position']['x'], y = data['position']['y'],
1122  z = data['position']['z'] / 100., color = data['color'],
1123  bright = data['bright'] / 100.,
1124  ambient = data['ambient'] / 100.)
1125  self._display.DrawLightingModel()
1126  if event.refresh:
1127  self.Refresh(False)
1128 
1129  def UpdateMap(self, render = True):
1130  """!Updates the canvas anytime there is a change to the
1131  underlaying images or to the geometry of the canvas.
1132 
1133  @param render re-render map composition
1134  """
1135  start = time.clock()
1136 
1137  self.resize = False
1138 
1139  if self.render['quick'] is False:
1140  self.parent.GetProgressBar().Show()
1141  self.parent.GetProgressBar().SetRange(2)
1142  self.parent.GetProgressBar().SetValue(0)
1143 
1144  if self.render['quick'] is False:
1145  self.parent.GetProgressBar().SetValue(1)
1146  self._display.Draw(False, -1)
1147  if self.saveHistory:
1148  self.ViewHistory(view = self.view, iview = self.iview)
1149  self.saveHistory = False
1150  elif self.render['quick'] is True:
1151  # quick
1152  mode = wxnviz.DRAW_QUICK_SURFACE | wxnviz.DRAW_QUICK_VOLUME
1153  if self.render['vlines']:
1154  mode |= wxnviz.DRAW_QUICK_VLINES
1155  if self.render['vpoints']:
1156  mode |= wxnviz.DRAW_QUICK_VPOINTS
1157  self._display.Draw(True, mode)
1158  else: # None -> reuse last rendered image
1159  pass # TODO
1160 
1161  self.SwapBuffers()
1162  # draw fringe after SwapBuffers, otherwise it don't have to be visible
1163  # on some computers
1164  if self.render['quick'] is False:
1165  self._display.DrawFringe()
1166  if self.decoration['arrow']['show']:
1167  self._display.DrawArrow()
1168  if self.decoration['scalebar']:
1169  self._display.DrawScalebar()
1170  if self.imagelist:
1171  if ((self.render['quick'] and self.dragid > -1) or # during dragging
1172  (not self.render['quick'] and self.dragid < 0)): # redraw
1173  self._display.Start2D()
1174  self.DrawImages()
1175 
1176 
1177 
1178  stop = time.clock()
1179 
1180  if self.render['quick'] is False:
1181  self.parent.GetProgressBar().SetValue(2)
1182  # hide process bar
1183  self.parent.GetProgressBar().Hide()
1184 
1185  Debug.msg(3, "GLWindow.UpdateMap(): quick = %d, -> time = %g" % \
1186  (self.render['quick'], (stop-start)))
1187 
1188  def EraseMap(self):
1189  """!Erase the canvas
1190  """
1191  self._display.EraseMap()
1192  self.SwapBuffers()
1193 
1194  def _getDecorationSize(self):
1195  """!Get initial size of north arrow/scalebar"""
1196  size = self._display.GetLongDim() / 8.
1197  coef = 0.01
1198  if size < 1:
1199  coef = 100.
1200  return int(size * coef)/coef
1201 
1202  def SetDrawArrow(self, pos):
1203 
1204  if self._display.SetArrow(pos[0], pos[1],
1205  self.decoration['arrow']['size'],
1206  self.decoration['arrow']['color']):
1207  self._display.DrawArrow()
1208  # update
1209  self.decoration['arrow']['show'] = True
1210  self.decoration['arrow']['position']['x'] = pos[0]
1211  self.decoration['arrow']['position']['y'] = pos[1]
1212  self.Refresh(False)
1213 
1214  def SetDrawScalebar(self, pos):
1215  """!Add scale bar, sets properties and draw"""
1216  if len(self.decoration['scalebar']) == 0:
1217  self.decoration['scalebar'].append(
1218  self.nvizDefault.SetDecorDefaultProp(type = 'scalebar')['scalebar'])
1219  self.decoration['scalebar'][0]['size'] = self._getDecorationSize()
1220  else:
1221  self.decoration['scalebar'].append(copy.deepcopy(self.decoration['scalebar'][-1]))
1222  self.decoration['scalebar'][-1]['id'] += 1
1223 
1224  ret = self._display.SetScalebar(self.decoration['scalebar'][-1]['id'], pos[0], pos[1],
1225  self.decoration['scalebar'][-1]['size'],
1226  self.decoration['scalebar'][-1]['color'])
1227  if ret:
1228  self._display.DrawScalebar()
1229  # update
1230  self.decoration['scalebar'][-1]['position']['x'] = pos[0]
1231  self.decoration['scalebar'][-1]['position']['y'] = pos[1]
1232  self.Refresh(False)
1233 
1234  def IsLoaded(self, item):
1235  """!Check if layer (item) is already loaded
1236 
1237  @param item layer item
1238  """
1239  layer = self.tree.GetPyData(item)[0]['maplayer']
1240  data = self.tree.GetPyData(item)[0]['nviz']
1241 
1242  if not data:
1243  return 0
1244 
1245  if layer.type == 'raster':
1246  if 'object' not in data['surface']:
1247  return 0
1248  elif layer.type == 'vector':
1249  if 'object' not in data['vlines'] and \
1250  'object' not in data['points']:
1251  return 0
1252 
1253  return 1
1254 
1255  def _GetDataLayers(self, item, litems):
1256  """!Return get list of enabled map layers"""
1257  # load raster & vector maps
1258  while item and item.IsOk():
1259  type = self.tree.GetPyData(item)[0]['type']
1260  if type == 'group':
1261  subItem = self.tree.GetFirstChild(item)[0]
1262  self._GetDataLayers(subItem, litems)
1263  item = self.tree.GetNextSibling(item)
1264 
1265  if not item.IsChecked() or \
1266  type not in ('raster', 'vector', '3d-raster'):
1267  item = self.tree.GetNextSibling(item)
1268  continue
1269 
1270  litems.append(item)
1271 
1272  item = self.tree.GetNextSibling(item)
1273 
1274  def LoadDataLayers(self):
1275  """!Load raster/vector from current layer tree
1276 
1277  @todo volumes
1278  """
1279  if not self.tree:
1280  return
1281 
1282  listOfItems = []
1283  item = self.tree.GetFirstChild(self.tree.root)[0]
1284  self._GetDataLayers(item, listOfItems)
1285 
1286  start = time.time()
1287 
1288  while(len(listOfItems) > 0):
1289  item = listOfItems.pop()
1290  type = self.tree.GetPyData(item)[0]['type']
1291  if item in self.layers:
1292  continue
1293  # "raster (double click to set properties)" - tries to load this
1294  # layer - no idea how to fix it
1295  if ' ' in self.tree.GetPyData(item)[0]['maplayer'].name:
1296  return
1297  try:
1298  if type == 'raster':
1299  self.LoadRaster(item)
1300  elif type == '3d-raster':
1301  self.LoadRaster3d(item)
1302  elif type == 'vector':
1303  layer = self.tree.GetPyData(item)[0]['maplayer']
1304  vInfo = grass.vector_info_topo(layer.GetName())
1305  if (vInfo['points']) > 0:
1306  # include vInfo['centroids'] to initially load centroids
1307  self.LoadVector(item, points = True)
1308  if (vInfo['lines'] + vInfo['boundaries']) > 0 or vInfo['map3d']:
1309  self.LoadVector(item, points = False)
1310 
1311  except GException, e:
1312  GError(parent = self,
1313  message = e.value)
1314 
1315  stop = time.time()
1316 
1317  Debug.msg(1, "GLWindow.LoadDataLayers(): time = %f" % (stop-start))
1318 
1319  def UnloadDataLayers(self, force = False):
1320  """!Unload any layers that have been deleted from layer tree
1321 
1322  @param force True to unload all data layers
1323  """
1324  if not self.tree:
1325  return
1326 
1327  listOfItems = []
1328  if not force:
1329  item = self.tree.GetFirstChild(self.tree.root)[0]
1330  self._GetDataLayers(item, listOfItems)
1331 
1332  start = time.time()
1333 
1334  update = False
1335  layersTmp = self.layers[:]
1336  for layer in layersTmp:
1337  if layer in listOfItems:
1338  continue
1339  ltype = self.tree.GetPyData(layer)[0]['type']
1340  try:
1341  if ltype == 'raster':
1342  self.UnloadRaster(layer)
1343  elif ltype == '3d-raster':
1344  self.UnloadRaster3d(layer)
1345  elif ltype == 'vector':
1346  maplayer = self.tree.GetPyData(layer)[0]['maplayer']
1347  vInfo = grass.vector_info_topo(maplayer.GetName())
1348  if (vInfo['points'] + vInfo['centroids']) > 0:
1349  self.UnloadVector(layer, points = True)
1350  if (vInfo['lines'] + vInfo['boundaries']) > 0 or vInfo['map3d']:
1351  self.UnloadVector(layer, points = False)
1352 
1353  except GException, e:
1354  GError(parent = self,
1355  message = e.value)
1356 
1357  if force and self.baseId > 0: # unload base surface when quitting
1358  ret = self._display.UnloadSurface(self.baseId)
1359  self.baseId = -1
1360  if update:
1361  self.lmgr.nviz.UpdateSettings()
1362  self.UpdateView(None)
1363 
1364  stop = time.time()
1365 
1366  Debug.msg(1, "GLWindow.UnloadDataLayers(): time = %f" % (stop-start))
1367 
1368  def SetVectorSurface(self, data):
1369  """!Set reference surfaces of vector"""
1370  data['mode']['surface'] = {}
1371  data['mode']['surface']['value'] = list()
1372  data['mode']['surface']['show'] = list()
1373  for name in self.GetLayerNames('raster'):
1374  data['mode']['surface']['value'].append(name)
1375  data['mode']['surface']['show'].append(True)
1376 
1377  def SetVectorFromCmd(self, item, data):
1378  """!Set 3D view properties from cmd (d.vect)
1379 
1380  @param item Layer Tree item
1381  @param nviz data
1382  """
1383  cmd = self.tree.GetPyData(item)[0]['cmd']
1384  if cmd[0] != 'd.vect':
1385  return
1386  for opt in cmd[1:]:
1387  try:
1388  key, value = opt.split('=')
1389  except ValueError:
1390  continue
1391  if key == 'color':
1392  data['lines']['color']['value'] = value
1393  data['points']['color']['value'] = value
1394 
1395  def SetMapObjProperties(self, item, id, nvizType):
1396  """!Set map object properties
1397 
1398  Properties must be afterwards updated by
1399  UpdateMapObjProperties().
1400 
1401  @param item layer item
1402  @param id nviz layer id (or -1)
1403  @param nvizType nviz data type (surface, points, vector)
1404  """
1405  if nvizType != 'constant':
1406  mapType = self.tree.GetPyData(item)[0]['maplayer'].type
1407  # reference to original layer properties (can be None)
1408  data = self.tree.GetPyData(item)[0]['nviz']
1409  else:
1410  mapType = nvizType
1411  data = self.constants[item]
1412 
1413  if not data:
1414  # init data structure
1415  if nvizType != 'constant':
1416  self.tree.GetPyData(item)[0]['nviz'] = {}
1417  data = self.tree.GetPyData(item)[0]['nviz']
1418 
1419  if mapType == 'raster':
1420  # reset to default properties
1421  data[nvizType] = self.nvizDefault.SetSurfaceDefaultProp()
1422 
1423  elif mapType == 'vector':
1424  # reset to default properties (lines/points)
1425  data['vector'] = self.nvizDefault.SetVectorDefaultProp()
1426  self.SetVectorFromCmd(item, data['vector'])
1427  self.SetVectorSurface(data['vector']['points'])
1428  self.SetVectorSurface(data['vector']['lines'])
1429 
1430  elif mapType == '3d-raster':
1431  # reset to default properties
1432  data[nvizType] = self.nvizDefault.SetVolumeDefaultProp()
1433 
1434  elif mapType == 'constant':
1435  data['constant'] = self.nvizDefault.SetConstantDefaultProp()
1436 
1437  else:
1438  # complete data (use default values), not sure if this is necessary
1439  if mapType == 'raster':
1440  if not data['surface']:
1441  data['surface'] = self.nvizDefault.SetSurfaceDefaultProp()
1442  if mapType == 'vector':
1443  if not data['vector']['lines']:
1444  self.nvizDefault.SetVectorLinesDefaultProp(data['vector']['lines'])
1445  if not data['vector']['points']:
1446  self.nvizDefault.SetVectorPointsDefaultProp(data['vector']['points'])
1447  # set updates
1448  for sec in data.keys():
1449  for sec1 in data[sec].keys():
1450  if sec1 == 'position':
1451  data[sec][sec1]['update'] = None
1452  continue
1453  if type(data[sec][sec1]) == types.DictType:
1454  for sec2 in data[sec][sec1].keys():
1455  if sec2 not in ('all', 'init', 'id'):
1456  data[sec][sec1][sec2]['update'] = None
1457  elif type(data[sec][sec1]) == types.ListType:
1458  for i in range(len(data[sec][sec1])):
1459  for sec2 in data[sec][sec1][i].keys():
1460  data[sec][sec1][i][sec2]['update'] = None
1461  event = wxUpdateProperties(data = data)
1462  wx.PostEvent(self, event)
1463 
1464  # set id
1465  if id > 0:
1466  if mapType in ('raster', '3d-raster'):
1467  data[nvizType]['object'] = { 'id' : id,
1468  'init' : False }
1469  elif mapType == 'vector':
1470  data['vector'][nvizType]['object'] = { 'id' : id,
1471  'init' : False }
1472  elif mapType == 'constant':
1473  data[nvizType]['object'] = { 'id' : id,
1474  'init' : False }
1475 
1476  return data
1477 
1478  def LoadRaster(self, item):
1479  """!Load 2d raster map and set surface attributes
1480 
1481  @param layer item
1482  """
1483  return self._loadRaster(item)
1484 
1485  def LoadRaster3d(self, item):
1486  """!Load 3d raster map and set surface attributes
1487 
1488  @param layer item
1489  """
1490  return self._loadRaster(item)
1491 
1492  def _loadRaster(self, item):
1493  """!Load 2d/3d raster map and set its attributes
1494 
1495  @param layer item
1496  """
1497  layer = self.tree.GetPyData(item)[0]['maplayer']
1498 
1499  if layer.type not in ('raster', '3d-raster'):
1500  return
1501 
1502  if layer.type == 'raster':
1503  id = self._display.LoadSurface(str(layer.name), None, None)
1504  nvizType = 'surface'
1505  errorMsg = _("Loading raster map")
1506  elif layer.type == '3d-raster':
1507  id = self._display.LoadVolume(str(layer.name), None, None)
1508  nvizType = 'volume'
1509  errorMsg = _("Loading 3d raster map")
1510  else:
1511  id = -1
1512 
1513  if id < 0:
1514  if layer.type in ('raster', '3d-raster'):
1515  self.log.WriteError("%s <%s> %s" % (errorMsg, layer.name, _("failed")))
1516  else:
1517  self.log.WriteError(_("Unsupported layer type '%s'") % layer.type)
1518 
1519  self.layers.append(item)
1520 
1521  # set default/workspace layer properties
1522  data = self.SetMapObjProperties(item, id, nvizType)
1523 
1524  # update properties
1525  event = wxUpdateProperties(data = data)
1526  wx.PostEvent(self, event)
1527 
1528  # update tools window
1529  if hasattr(self.lmgr, "nviz") and \
1530  item == self.GetSelectedLayer(type = 'item'):
1531  toolWin = self.lmgr.nviz
1532  if layer.type == 'raster':
1533  win = toolWin.FindWindowById( \
1534  toolWin.win['vector']['lines']['surface'])
1535  win.SetItems(self.GetLayerNames(layer.type))
1536 
1537  #toolWin.UpdatePage(nvizType)
1538  #toolWin.SetPage(nvizType)
1539 
1540  return id
1541 
1542  def NewConstant(self):
1543  """!Create new constant"""
1544  index = len(self.constants)
1545  try:
1546  name = self.constants[-1]['constant']['object']['name'] + 1
1547  except IndexError:
1548  name = 1
1549  data = dict()
1550  self.constants.append(data)
1551  data = self.SetMapObjProperties(item = index, id = -1, nvizType = 'constant')
1552  self.AddConstant(data, name)
1553  return name
1554 
1555  def AddConstant(self, data, name):
1556  """!Add new constant"""
1557  id = self._display.AddConstant(value = data['constant']['value'], color = data['constant']['color'])
1558  self._display.SetSurfaceRes(id, data['constant']['resolution'], data['constant']['resolution'])
1559  data['constant']['object'] = { 'id' : id,
1560  'name': name,
1561  'init' : False }
1562 
1563  def DeleteConstant(self, index):
1564  """!Delete constant layer"""
1565  id = self.constants[index]['constant']['object']['id']
1566  self._display.UnloadSurface(id)
1567  del self.constants[index]
1568 
1569  def SelectCPlane(self, index):
1570  """!Select cutting plane"""
1571  for plane in range (self._display.GetCPlanesCount()):
1572  if plane == index:
1573  self._display.SelectCPlane(plane)
1574  self.cplanes[plane]['on'] = True
1575  self._display.SetFenceColor(self.cplanes[plane]['shading'])
1576  else:
1577  self._display.UnselectCPlane(plane)
1578  try:
1579  self.cplanes[plane]['on'] = False
1580  except IndexError:
1581  pass
1582 
1583  def OnUpdateCPlane(self, event):
1584  """!Change cutting plane settings"""
1585  self.UpdateCPlane(event.current, event.update)
1586 
1587  def UpdateCPlane(self, index, changes):
1588  """!Change cutting plane settings"""
1589  for each in changes:
1590  if each == 'rotation':
1591  self._display.SetCPlaneRotation(0, self.cplanes[index]['rotation']['tilt'],
1592  self.cplanes[index]['rotation']['rot'])
1593  if each == 'position':
1594  self._display.SetCPlaneTranslation(self.cplanes[index]['position']['x'],
1595  self.cplanes[index]['position']['y'],
1596  self.cplanes[index]['position']['z'])
1597  if each == 'shading':
1598  self._display.SetFenceColor(self.cplanes[index]['shading'])
1599 
1600  def UnloadRaster(self, item):
1601  """!Unload 2d raster map
1602 
1603  @param layer item
1604  """
1605  return self._unloadRaster(item)
1606 
1607  def UnloadRaster3d(self, item):
1608  """!Unload 3d raster map
1609 
1610  @param layer item
1611  """
1612  return self._unloadRaster(item)
1613 
1614  def _unloadRaster(self, item):
1615  """!Unload 2d/3d raster map
1616 
1617  @param item layer item
1618  """
1619  layer = self.tree.GetPyData(item)[0]['maplayer']
1620 
1621  if layer.type not in ('raster', '3d-raster'):
1622  return
1623 
1624  data = self.tree.GetPyData(item)[0]['nviz']
1625 
1626  if layer.type == 'raster':
1627  nvizType = 'surface'
1628  unloadFn = self._display.UnloadSurface
1629  errorMsg = _("Unable to unload raster map")
1630  successMsg = _("Raster map")
1631  else:
1632  nvizType = 'volume'
1633  unloadFn = self._display.UnloadVolume
1634  errorMsg = _("Unable to unload 3d raster map")
1635  successMsg = _("3d raster map")
1636 
1637  try:
1638  id = data[nvizType]['object']['id']
1639  except KeyError:
1640  return
1641 
1642  if unloadFn(id) == 0:
1643  self.log.WriteError("%s <%s>" % (errorMsg, layer.name))
1644  else:
1645  self.log.WriteLog("%s <%s> %s" % (successMsg, layer.name, _("unloaded successfully")))
1646 
1647  data[nvizType].pop('object')
1648 
1649  self.layers.remove(item)
1650 
1651  # update tools window
1652  if hasattr(self.lmgr, "nviz"):
1653  toolWin = self.lmgr.nviz
1654  if layer.type == 'raster':
1655  win = toolWin.FindWindowById(toolWin.win['vector']['lines']['surface'])
1656  win.SetItems(self.GetLayerNames(layer.type))
1657  win = toolWin.FindWindowById(toolWin.win['surface']['map'])
1658  win.SetValue('')
1659  if layer.type == '3d-raster':
1660  win = toolWin.FindWindowById(toolWin.win['volume']['map'])
1661  win.SetValue('')
1662  if layer.type == 'vector':
1663  win = toolWin.FindWindowById(toolWin.win['vector']['map'])
1664  win.SetValue('')
1665 
1666  def LoadVector(self, item, points = None, append = True):
1667  """!Load 2D or 3D vector map overlay
1668 
1669  @param item layer item
1670  @param points True to load points, False to load lines, None
1671  to load both
1672  @param append append vector to layer list
1673  """
1674  layer = self.tree.GetPyData(item)[0]['maplayer']
1675  if layer.type != 'vector':
1676  return
1677 
1678  # set default properties
1679  if points is None:
1680  self.SetMapObjProperties(item, -1, 'lines')
1681  self.SetMapObjProperties(item, -1, 'points')
1682  vecTypes = ('points', 'lines')
1683  elif points:
1684  self.SetMapObjProperties(item, -1, 'points')
1685  vecTypes = ('points', )
1686  else:
1687  self.SetMapObjProperties(item, -1, 'lines')
1688  vecTypes = ('lines', )
1689 
1690  id = -1
1691  for vecType in vecTypes:
1692  if vecType == 'lines':
1693  id, baseId = self._display.LoadVector(str(layer.GetName()), False)
1694  else:
1695  id, baseId = self._display.LoadVector(str(layer.GetName()), True)
1696  if id < 0:
1697  self.log.WriteError(_("Loading vector map <%(name)s> (%(type)s) failed") % \
1698  { 'name' : layer.name, 'type' : vecType })
1699  # update layer properties
1700  self.SetMapObjProperties(item, id, vecType)
1701  if baseId > 0:
1702  self.baseId = baseId # id of base surface (when no surface is loaded)
1703  if append:
1704  self.layers.append(item)
1705 
1706  # update properties
1707  data = self.tree.GetPyData(item)[0]['nviz']
1708  event = wxUpdateProperties(data = data)
1709  wx.PostEvent(self, event)
1710 
1711  # update tools window
1712  if hasattr(self.lmgr, "nviz") and \
1713  item == self.GetSelectedLayer(type = 'item'):
1714  toolWin = self.lmgr.nviz
1715 
1716  toolWin.UpdatePage('vector')
1717  ### toolWin.SetPage('vector')
1718 
1719  return id
1720 
1721  def UnloadVector(self, item, points = None, remove = True):
1722  """!Unload vector map overlay
1723 
1724  @param item layer item
1725  @param points,lines True to unload given feature type
1726  @param remove remove layer from list
1727  """
1728  layer = self.tree.GetPyData(item)[0]['maplayer']
1729  data = self.tree.GetPyData(item)[0]['nviz']['vector']
1730 
1731  # if vecType is None:
1732  # vecType = []
1733  # for v in ('lines', 'points'):
1734  # if UserSettings.Get(group = 'nviz', key = 'vector',
1735  # subkey = [v, 'show']):
1736  # vecType.append(v)
1737 
1738  if points is None:
1739  vecTypes = ('points', 'lines')
1740  elif points:
1741  vecTypes = ('points', )
1742  else:
1743  vecTypes = ('lines', )
1744 
1745  for vecType in vecTypes:
1746  if 'object' not in data[vecType]:
1747  continue
1748 
1749  id = data[vecType]['object']['id']
1750 
1751  if vecType == 'lines':
1752  ret = self._display.UnloadVector(id, False)
1753  else:
1754  ret = self._display.UnloadVector(id, True)
1755  if ret == 0:
1756  self.log.WriteError(_("Unable to unload vector map <%(name)s> (%(type)s)") % \
1757  { 'name': layer.name, 'type' : vecType })
1758  else:
1759  self.log.WriteLog(_("Vector map <%(name)s> (%(type)s) unloaded successfully") % \
1760  { 'name' : layer.name, 'type' : vecType })
1761 
1762  data[vecType].pop('object')
1763 
1764  if remove and item in self.layers:
1765  self.layers.remove(item)
1766 
1767  def ResetView(self):
1768  """!Reset to default view"""
1769  zexagOriginal, \
1770  self.iview['height']['value'], \
1771  self.iview['height']['min'], \
1772  self.iview['height']['max'] = self._display.SetViewDefault()
1773 
1774  ## hack for latlon projection
1775  ## TODO find more precise way or better rewrite it in OGSF
1776  self.iview['z-exag']['llRatio'] = 1
1777  if grass.locn_is_latlong():
1778  self.iview['z-exag']['llRatio'] = \
1779  math.pi / 180 * 6371000 * math.cos((grass.region()['n'] + grass.region()['s']) / 2)
1780 
1781  self.view['z-exag']['value'] = round(zexagOriginal * self.iview['z-exag']['llRatio'])
1782  self.view['z-exag']['min'] = UserSettings.Get(group = 'nviz', key = 'view',
1783  subkey = ('z-exag', 'min'))
1784  zexagMax = UserSettings.Get(group = 'nviz', key = 'view',
1785  subkey = ('z-exag', 'max'))
1786  if zexagMax <= self.view['z-exag']['value']:
1787  self.view['z-exag']['max'] = self.view['z-exag']['value'] * 2
1788  elif self.view['z-exag']['value'] < 1:
1789  if self.view['z-exag']['value'] == 0:
1790  self.view['z-exag']['value'] = 1
1791  self.view['z-exag']['max'] = 10 * self.view['z-exag']['value']
1792  else:
1793  self.view['z-exag']['max'] = zexagMax
1794 
1795  self.view['position']['x'] = UserSettings.Get(group = 'nviz', key = 'view',
1796  subkey = ('position', 'x'))
1797  self.view['position']['y'] = UserSettings.Get(group = 'nviz', key = 'view',
1798  subkey = ('position', 'y'))
1799  self.view['persp']['value'] = UserSettings.Get(group = 'nviz', key = 'view',
1800  subkey = ('persp', 'value'))
1801 
1802  self.view['twist']['value'] = UserSettings.Get(group = 'nviz', key = 'view',
1803  subkey = ('twist', 'value'))
1804  self._display.ResetRotation()
1805  self.iview['rotation'] = None
1806  self._display.LookAtCenter()
1807  focus = self.iview['focus']
1808  focus['x'], focus['y'], focus['z'] = self._display.GetFocus()
1809 
1810  self.PostViewEvent()
1811 
1812  def UpdateMapObjProperties(self, event):
1813  """!Generic method to update data layer properties"""
1814  data = event.data
1815 
1816  if 'surface' in data:
1817  try:
1818  id = data['surface']['object']['id']
1819  except KeyError:
1820  return
1821  self.UpdateSurfaceProperties(id, data['surface'])
1822  # -> initialized
1823  data['surface']['object']['init'] = True
1824 
1825  elif 'constant' in data:
1826  id = data['constant']['object']['id']
1827  self.UpdateConstantProperties(id, data['constant'])
1828  # -> initialized
1829  data['constant']['object']['init'] = True
1830 
1831  elif 'volume' in data:
1832  id = data['volume']['object']['id']
1833  self.UpdateVolumeProperties(id, data['volume'])
1834  # -> initialized
1835  data['volume']['object']['init'] = True
1836 
1837  elif 'vector' in data:
1838  for type in ('lines', 'points'):
1839  if 'object' in data['vector'][type]:
1840  id = data['vector'][type]['object']['id']
1841  self.UpdateVectorProperties(id, data['vector'], type)
1842  # -> initialized
1843  data['vector'][type]['object']['init'] = True
1844 
1845  def UpdateConstantProperties(self, id, data):
1846  """!Update surface map object properties"""
1847  self._display.SetSurfaceColor(id = id, map = False, value = data['color'])
1848  self._display.SetSurfaceTopo(id = id, map = False, value = data['value'])
1849  self._display.SetSurfaceRes(id, data['resolution'], data['resolution'])
1850  if data['transp'] == 0:
1851  self._display.UnsetSurfaceTransp(id)
1852  else:
1853  self._display.SetSurfaceTransp(id, map = False, value = data['transp'])
1854 
1855  def UpdateSurfaceProperties(self, id, data):
1856  """!Update surface map object properties"""
1857  # surface attributes
1858  for attrb in ('color', 'mask',
1859  'transp', 'shine'):
1860  if attrb not in data['attribute'] or \
1861  'update' not in data['attribute'][attrb]:
1862  continue
1863 
1864  map = data['attribute'][attrb]['map']
1865  value = data['attribute'][attrb]['value']
1866 
1867  if map is None: # unset
1868  # only optional attributes
1869  if attrb == 'mask':
1870  # TODO: invert mask
1871  # TODO: broken in NVIZ
1872  self._display.UnsetSurfaceMask(id)
1873  elif attrb == 'transp':
1874  self._display.UnsetSurfaceTransp(id)
1875  else:
1876  if type(value) == types.StringType and \
1877  len(value) <= 0: # ignore empty values (TODO: warning)
1878  continue
1879  if attrb == 'color':
1880  self._display.SetSurfaceColor(id, map, str(value))
1881  elif attrb == 'mask':
1882  # TODO: invert mask
1883  # TODO: broken in NVIZ
1884  self._display.SetSurfaceMask(id, False, str(value))
1885  elif attrb == 'transp':
1886  self._display.SetSurfaceTransp(id, map, str(value))
1887  elif attrb == 'shine':
1888  self._display.SetSurfaceShine(id, map, str(value))
1889  data['attribute'][attrb].pop('update')
1890 
1891  # draw res
1892  if 'update' in data['draw']['resolution']:
1893  coarse = data['draw']['resolution']['coarse']
1894  fine = data['draw']['resolution']['fine']
1895 
1896  if data['draw']['all']:
1897  self._display.SetSurfaceRes(-1, fine, coarse)
1898  else:
1899  self._display.SetSurfaceRes(id, fine, coarse)
1900  data['draw']['resolution'].pop('update')
1901 
1902  # draw style
1903  if 'update' in data['draw']['mode']:
1904  if data['draw']['mode']['value'] < 0: # need to calculate
1905  data['draw']['mode']['value'] = \
1906  self.nvizDefault.GetDrawMode(mode = data['draw']['mode']['desc']['mode'],
1907  style = data['draw']['mode']['desc']['style'],
1908  shade = data['draw']['mode']['desc']['shading'],
1909  string = True)
1910  style = data['draw']['mode']['value']
1911  if data['draw']['all']:
1912  self._display.SetSurfaceStyle(-1, style)
1913  else:
1914  self._display.SetSurfaceStyle(id, style)
1915  data['draw']['mode'].pop('update')
1916 
1917  # wire color
1918  if 'update' in data['draw']['wire-color']:
1919  color = data['draw']['wire-color']['value']
1920  if data['draw']['all']:
1921  self._display.SetWireColor(-1, str(color))
1922  else:
1923  self._display.SetWireColor(id, str(color))
1924  data['draw']['wire-color'].pop('update')
1925 
1926  # position
1927  if 'update' in data['position']:
1928  x = data['position']['x']
1929  y = data['position']['y']
1930  z = data['position']['z']
1931  self._display.SetSurfacePosition(id, x, y, z)
1932  data['position'].pop('update')
1933  data['draw']['all'] = False
1934 
1935  def UpdateVolumeProperties(self, id, data, isosurfId = None):
1936  """!Update volume (isosurface/slice) map object properties"""
1937  if 'update' in data['draw']['resolution']:
1938  if data['draw']['mode']['value'] == 0:
1939  self._display.SetIsosurfaceRes(id, data['draw']['resolution']['isosurface']['value'])
1940  else:
1941  self._display.SetSliceRes(id, data['draw']['resolution']['slice']['value'])
1942  data['draw']['resolution'].pop('update')
1943 
1944  if 'update' in data['draw']['shading']:
1945  if data['draw']['mode']['value'] == 0:
1946  if data['draw']['shading']['isosurface']['value'] < 0: # need to calculate
1947  mode = data['draw']['shading']['isosurface']['value'] = \
1948  self.nvizDefault.GetDrawMode(shade = data['draw']['shading']['isosurface'],
1949  string = False)
1950  self._display.SetIsosurfaceMode(id, mode)
1951  else:
1952  if data['draw']['shading']['slice']['value'] < 0: # need to calculate
1953  mode = data['draw']['shading']['slice']['value'] = \
1954  self.nvizDefault.GetDrawMode(shade = data['draw']['shading']['slice'],
1955  string = False)
1956  self._display.SetSliceMode(id, mode)
1957  data['draw']['shading'].pop('update')
1958 
1959  #
1960  # isosurface attributes
1961  #
1962  isosurfId = 0
1963  for isosurf in data['isosurface']:
1964  self._display.AddIsosurface(id, 0, isosurf_id = isosurfId)
1965  for attrb in ('topo', 'color', 'mask',
1966  'transp', 'shine'):
1967  if attrb not in isosurf or \
1968  'update' not in isosurf[attrb]:
1969  continue
1970  map = isosurf[attrb]['map']
1971  value = isosurf[attrb]['value']
1972 
1973  if map is None: # unset
1974  # only optional attributes
1975  if attrb == 'topo' :
1976  self._display.SetIsosurfaceTopo(id, isosurfId, map, str(value))
1977  elif attrb == 'mask':
1978  # TODO: invert mask
1979  # TODO: broken in NVIZ
1980  self._display.UnsetIsosurfaceMask(id, isosurfId)
1981  elif attrb == 'transp':
1982  self._display.UnsetIsosurfaceTransp(id, isosurfId)
1983  else:
1984  if type(value) == types.StringType and \
1985  len(value) <= 0: # ignore empty values (TODO: warning)
1986  continue
1987  elif attrb == 'color':
1988  self._display.SetIsosurfaceColor(id, isosurfId, map, str(value))
1989  elif attrb == 'mask':
1990  # TODO: invert mask
1991  # TODO: broken in NVIZ
1992  self._display.SetIsosurfaceMask(id, isosurfId, False, str(value))
1993  elif attrb == 'transp':
1994  self._display.SetIsosurfaceTransp(id, isosurfId, map, str(value))
1995  elif attrb == 'shine':
1996  self._display.SetIsosurfaceShine(id, isosurfId, map, str(value))
1997  isosurf[attrb].pop('update')
1998  isosurfId += 1
1999  #
2000  # slice attributes
2001  #
2002  sliceId = 0
2003  for slice in data['slice']:
2004  ret = self._display.AddSlice(id, slice_id = sliceId)
2005  if 'update' in slice['position']:
2006  pos = slice['position']
2007  ret = self._display.SetSlicePosition(id, sliceId, pos['x1'], pos['x2'],
2008  pos['y1'], pos['y2'], pos['z1'], pos['z2'], pos['axis'])
2009 
2010  slice['position'].pop('update')
2011  if 'update' in slice['transp']:
2012  tr = slice['transp']['value']
2013  self._display.SetSliceTransp(id, sliceId, tr)
2014  sliceId += 1
2015 
2016  # position
2017  if 'update' in data['position'] and 'x' in data['position']:
2018  x = data['position']['x']
2019  y = data['position']['y']
2020  z = data['position']['z']
2021  self._display.SetVolumePosition(id, x, y, z)
2022  data['position'].pop('update')
2023 
2024  def UpdateVectorProperties(self, id, data, type):
2025  """!Update vector layer properties
2026 
2027  @param id layer id
2028  @param data properties
2029  @param type lines/points
2030  """
2031  if type == 'points':
2032  self.UpdateVectorPointsProperties(id, data[type])
2033  else:
2034  self.UpdateVectorLinesProperties(id, data[type])
2035 
2036  def UpdateVectorLinesProperties(self, id, data):
2037  """!Update vector line map object properties"""
2038  # mode
2039  if 'update' in data['color'] or \
2040  'update' in data['width'] or \
2041  'update' in data['mode']:
2042  width = data['width']['value']
2043  color = data['color']['value']
2044  if data['mode']['type'] == 'flat':
2045  flat = True
2046  if 'surface' in data['mode']:
2047  data['mode'].pop('surface')
2048  else:
2049  flat = False
2050 
2051  self._display.SetVectorLineMode(id, color,
2052  width, flat)
2053 
2054  if 'update' in data['color']:
2055  data['color'].pop('update')
2056  if 'update' in data['width']:
2057  data['width'].pop('update')
2058 
2059  # height
2060  if 'update' in data['height']:
2061  self._display.SetVectorLineHeight(id,
2062  data['height']['value'])
2063  data['height'].pop('update')
2064 
2065  # surface
2066  if 'surface' in data['mode'] and 'update' in data['mode']:
2067  for item in range(len(data['mode']['surface']['value'])):
2068  for type in ('raster', 'constant'):
2069  sid = self.GetLayerId(type = type,
2070  name = data['mode']['surface']['value'][item])
2071  if sid > -1:
2072  if data['mode']['surface']['show'][item]:
2073  self._display.SetVectorLineSurface(id, sid)
2074  else:
2075  self._display.UnsetVectorLineSurface(id, sid)
2076  break
2077 
2078  if 'update' in data['mode']:
2079  data['mode'].pop('update')
2080 
2081  def UpdateVectorPointsProperties(self, id, data):
2082  """!Update vector point map object properties"""
2083  if 'update' in data['size'] or \
2084  'update' in data['width'] or \
2085  'update' in data['marker'] or \
2086  'update' in data['color']:
2087 
2088  ret = self._display.SetVectorPointMode(id, data['color']['value'],
2089  data['width']['value'], float(data['size']['value']),
2090  data['marker']['value'] + 1)
2091 
2092  error = None
2093  if ret == -1:
2094  error = _("Vector point layer not found (id = %d)") % id
2095  elif ret == -2:
2096  error = _("Unable to set data layer properties (id = %d)") % id
2097 
2098  if error:
2099  raise GException(_("Setting data layer properties failed.\n\n%s") % error)
2100 
2101  for prop in ('size', 'width', 'marker', 'color'):
2102  if 'update' in data[prop]:
2103  data[prop].pop('update')
2104 
2105  # height
2106  if 'update' in data['height']:
2107  self._display.SetVectorPointHeight(id,
2108  data['height']['value'])
2109  data['height'].pop('update')
2110 
2111  # surface
2112  if 'update' in data['mode'] and 'surface' in data['mode']:
2113  for item in range(len(data['mode']['surface']['value'])):
2114  for type in ('raster', 'constant'):
2115  sid = self.GetLayerId(type = type,
2116  name = data['mode']['surface']['value'][item])
2117  if sid > -1:
2118  if data['mode']['surface']['show'][item]:
2119  self._display.SetVectorPointSurface(id, sid)
2120  else:
2121  self._display.UnsetVectorPointSurface(id, sid)
2122  break
2123  data['mode'].pop('update')
2124 
2125  def GetLayerNames(self, type):
2126  """!Return list of map layer names of given type"""
2127  layerName = []
2128 
2129  if type == 'constant':
2130  for item in self.constants:
2131  layerName.append(_("constant#") + str(item['constant']['object']['name']))
2132  else:
2133  for item in self.layers:
2134  mapLayer = self.tree.GetPyData(item)[0]['maplayer']
2135  if type != mapLayer.GetType():
2136  continue
2137 
2138  layerName.append(mapLayer.GetName())
2139 
2140  return layerName
2141 
2142  def GetLayerId(self, type, name, vsubtyp = None):
2143  """!Get layer object id or -1"""
2144  if len(name) < 1:
2145  return -1
2146 
2147  if type == 'constant':
2148  for item in self.constants:
2149  if _("constant#") + str(item['constant']['object']['name']) == name:
2150  return item['constant']['object']['id']
2151 
2152 
2153  for item in self.layers:
2154  mapLayer = self.tree.GetPyData(item)[0]['maplayer']
2155  if type != mapLayer.GetType() or \
2156  name != mapLayer.GetName():
2157  continue
2158 
2159  data = self.tree.GetPyData(item)[0]['nviz']
2160 
2161  try:
2162  if type == 'raster':
2163  return data['surface']['object']['id']
2164  elif type == 'vector':
2165  if vsubtyp == 'vpoint':
2166  return data['vector']['points']['object']['id']
2167  elif vsubtyp == 'vline':
2168  return data['vector']['lines']['object']['id']
2169  elif type == '3d-raster':
2170  return data['volume']['object']['id']
2171  except KeyError:
2172  return -1
2173  return -1
2174 
2175  def ReloadLayersData(self):
2176  """!Delete nviz data of all loaded layers and reload them from current settings"""
2177  for item in self.layers:
2178  type = self.tree.GetPyData(item)[0]['type']
2179  layer = self.tree.GetPyData(item)[0]['maplayer']
2180  data = self.tree.GetPyData(item)[0]['nviz']
2181 
2182  if type == 'raster':
2183  self.nvizDefault.SetSurfaceDefaultProp(data['surface'])
2184  if type == 'vector':
2185  vInfo = grass.vector_info_topo(layer.GetName())
2186  if (vInfo['points'] + vInfo['centroids']) > 0:
2187  self.nvizDefault.SetVectorPointsDefaultProp(data['vector']['points'])
2188  if (vInfo['lines'] + vInfo['boundaries']) > 0:
2189  self.nvizDefault.SetVectorLinesDefaultProp(data['vector']['lines'])
2190 
2191  def NvizCmdCommand(self):
2192  """!Generate command for m.nviz.image according to current state"""
2193  cmd = 'm.nviz.image '
2194 
2195  rasters = []
2196  vectors = []
2197  volumes = []
2198  for item in self.layers:
2199  if self.tree.GetPyData(item)[0]['type'] == 'raster':
2200  rasters.append(item)
2201  elif self.tree.GetPyData(item)[0]['type'] == '3d-raster':
2202  volumes.append(item)
2203  elif self.tree.GetPyData(item)[0]['type'] == 'vector':
2204  vectors.append(item)
2205  if not rasters and not self.constants:
2206  return _("At least one raster map required")
2207  # elevation_map/elevation_value
2208  if self.constants:
2209  subcmd = "elevation_value="
2210  for constant in self.constants:
2211  subcmd += "%d," % constant['constant']['value']
2212  subcmd = subcmd.strip(', ') + ' '
2213  cmd += subcmd
2214  if rasters:
2215  subcmd = "elevation_map="
2216  for item in rasters:
2217  subcmd += "%s," % self.tree.GetPyData(item)[0]['maplayer'].GetName()
2218  subcmd = subcmd.strip(', ') + ' '
2219  cmd += subcmd
2220  #
2221  # draw mode
2222  #
2223  cmdMode = "mode="
2224  cmdFine = "resolution_fine="
2225  cmdCoarse = "resolution_coarse="
2226  cmdShading = "shading="
2227  cmdStyle = "style="
2228  cmdWire = "wire_color="
2229  # test -a flag
2230  flag_a = "-a "
2231  nvizDataFirst = self.tree.GetPyData(rasters[0])[0]['nviz']['surface']['draw']
2232  for item in rasters:
2233  nvizData = self.tree.GetPyData(item)[0]['nviz']['surface']['draw']
2234  if nvizDataFirst != nvizData:
2235  flag_a = ""
2236  cmd += flag_a
2237  for item in rasters:
2238  nvizData = self.tree.GetPyData(item)[0]['nviz']['surface']['draw']
2239 
2240  cmdMode += "%s," % nvizData['mode']['desc']['mode']
2241  cmdFine += "%s," % nvizData['resolution']['fine']
2242  cmdCoarse += "%s," % nvizData['resolution']['coarse']
2243  cmdShading += "%s," % nvizData['mode']['desc']['shading']
2244  cmdStyle += "%s," % nvizData['mode']['desc']['style']
2245  cmdWire += "%s," % nvizData['wire-color']['value']
2246  for item in self.constants:
2247  cmdMode += "fine,"
2248  cmdFine += "%s," % item['constant']['resolution']
2249  cmdCoarse += "%s," % item['constant']['resolution']
2250  cmdShading += "gouraud,"
2251  cmdStyle += "surface,"
2252  cmdWire += "0:0:0,"
2253  mode = []
2254  for subcmd in (cmdMode, cmdFine, cmdCoarse, cmdShading, cmdStyle, cmdWire):
2255  if flag_a:
2256  mode.append(subcmd.split(',')[0] + ' ')
2257  else:
2258  subcmd = subcmd.strip(', ') + ' '
2259  cmd += subcmd
2260  if flag_a:# write only meaningful possibilities
2261  cmd += mode[0]
2262  if 'fine' in mode[0]:
2263  cmd += mode[1]
2264  elif 'coarse' in mode[0]:
2265  cmd += mode[2]
2266  elif 'both' in mode[0]:
2267  cmd += mode[2]
2268  cmd += mode[1]
2269  if 'flat' in mode[3]:
2270  cmd += mode[3]
2271  if 'wire' in mode[4]:
2272  cmd += mode[4]
2273  if 'coarse' in mode[0] or 'both' in mode[0] and 'wire' in mode[3]:
2274  cmd += mode[5]
2275  #
2276  # attributes
2277  #
2278  cmdColorMap = "color_map="
2279  cmdColorVal = "color="
2280  for item in rasters:
2281  nvizData = self.tree.GetPyData(item)[0]['nviz']['surface']['attribute']
2282  if 'color' not in nvizData:
2283  cmdColorMap += "%s," % self.tree.GetPyData(item)[0]['maplayer'].GetName()
2284  else:
2285  if nvizData['color']['map']:
2286  cmdColorMap += "%s," % nvizData['color']['value']
2287  else:
2288  cmdColorVal += "%s," % nvizData['color']['value']
2289  #TODO
2290  # transparency, shine, mask
2291  for item in self.constants:
2292  cmdColorVal += "%s," % item['constant']['color']
2293  if cmdColorMap.split("=")[1]:
2294  cmd += cmdColorMap.strip(', ') + ' '
2295  if cmdColorVal.split("=")[1]:
2296  cmd += cmdColorVal.strip(', ') + ' '
2297  cmd += "\\\n"
2298  #
2299  # vlines
2300  #
2301  if vectors:
2302  cmdLines = cmdLWidth = cmdLHeight = cmdLColor = cmdLMode = cmdLPos = \
2303  cmdPoints = cmdPWidth = cmdPSize = cmdPColor = cmdPMarker = cmdPPos = cmdPLayer = ""
2304  markers = ['x', 'box', 'sphere', 'cube', 'diamond',
2305  'dec_tree', 'con_tree', 'aster', 'gyro', 'histogram']
2306  for vector in vectors:
2307  layerName = self.tree.GetPyData(vector)[0]['maplayer'].GetName()
2308  vInfo = grass.vector_info_topo(layerName)
2309  nvizData = self.tree.GetPyData(vector)[0]['nviz']['vector']
2310  if (vInfo['lines'] + vInfo['boundaries']) > 0:
2311  cmdLines += "%s," % self.tree.GetPyData(vector)[0]['maplayer'].GetName()
2312  cmdLWidth += "%d," % nvizData['lines']['width']['value']
2313  cmdLHeight += "%d," % nvizData['lines']['height']['value']
2314  cmdLColor += "%s," % nvizData['lines']['color']['value']
2315  cmdLMode += "%s," % nvizData['lines']['mode']['type']
2316  cmdLPos += "0,0,%d," % nvizData['lines']['height']['value']
2317  if (vInfo['points'] + vInfo['centroids']) > 0:
2318  cmdPoints += "%s," % self.tree.GetPyData(vector)[0]['maplayer'].GetName()
2319  cmdPWidth += "%d," % nvizData['points']['width']['value']
2320  cmdPSize += "%d," % nvizData['points']['size']['value']
2321  cmdPColor += "%s," % nvizData['points']['color']['value']
2322  cmdPMarker += "%s," % markers[nvizData['points']['marker']['value']]
2323  cmdPPos += "0,0,%d," % nvizData['points']['height']['value']
2324  cmdPLayer += "1,1,"
2325  if cmdLines:
2326  cmd += "vline=" + cmdLines.strip(',') + ' '
2327  cmd += "vline_width=" + cmdLWidth.strip(',') + ' '
2328  cmd += "vline_color=" + cmdLColor.strip(',') + ' '
2329  cmd += "vline_height=" + cmdLHeight.strip(',') + ' '
2330  cmd += "vline_mode=" + cmdLMode.strip(',') + ' '
2331  cmd += "vline_position=" + cmdLPos.strip(',') + ' '
2332  if cmdPoints:
2333  cmd += "vpoint=" + cmdPoints.strip(',') + ' '
2334  cmd += "vpoint_width=" + cmdPWidth.strip(',') + ' '
2335  cmd += "vpoint_color=" + cmdPColor.strip(',') + ' '
2336  cmd += "vpoint_size=" + cmdPSize.strip(',') + ' '
2337  cmd += "vpoint_marker=" + cmdPMarker.strip(',') + ' '
2338  cmd += "vpoint_position=" + cmdPPos.strip(',') + ' '
2339  cmd += "\\\n"
2340 
2341  #
2342  # volumes
2343  #
2344  if volumes:
2345  cmdName = cmdShade = cmdRes = cmdPos = cmdIso = ""
2346  cmdIsoColorMap = cmdIsoColorVal = cmdIsoTrMap = cmdIsoTrVal = ""
2347  cmdSlice = cmdSliceTransp = cmdSlicePos = ""
2348  for i, volume in enumerate(volumes):
2349  nvizData = self.tree.GetPyData(volume)[0]['nviz']['volume']
2350  cmdName += "%s," % self.tree.GetPyData(volume)[0]['maplayer'].GetName()
2351  cmdShade += "%s," % nvizData['draw']['shading']['isosurface']['desc']
2352  cmdRes += "%d," % nvizData['draw']['resolution']['isosurface']['value']
2353  if nvizData['position']:
2354  cmdPos += "%d,%d,%d," % (nvizData['position']['x'], nvizData['position']['y'],
2355  nvizData['position']['z'])
2356  for iso in nvizData['isosurface']:
2357  level = iso['topo']['value']
2358  cmdIso += "%d:%s," % (i + 1, level)
2359  if iso['color']['map']:
2360  cmdIsoColorMap += "%s," % iso['color']['value']
2361  else:
2362  cmdIsoColorVal += "%s," % iso['color']['value']
2363  if 'transp' in iso:
2364  if iso['transp']['map']:
2365  cmdIsoTrMap += "%s," % iso['transp']['value']
2366  else:
2367  cmdIsoTrVal += "%s," % iso['transp']['value']
2368 
2369  for slice in nvizData['slice']:
2370  axis = ('x','y','z')[slice['position']['axis']]
2371  cmdSlice += "%d:%s," % (i + 1, axis)
2372  for coord in ('x1', 'x2', 'y1', 'y2', 'z1', 'z2'):
2373  cmdSlicePos += "%f," % slice['position'][coord]
2374  cmdSliceTransp += "%s," % slice['transp']['value']
2375 
2376  cmd += "volume=" + cmdName.strip(',') + ' '
2377  cmd += "volume_shading=" + cmdShade.strip(',') + ' '
2378  cmd += "volume_resolution=" + cmdRes.strip(',') + ' '
2379  if nvizData['position']:
2380  cmd += "volume_position=" + cmdPos.strip(',') + ' '
2381  if cmdIso:
2382  cmd += "isosurf_level=" + cmdIso.strip(',') + ' '
2383  if cmdIsoColorMap:
2384  cmd += "isosurf_color_map=" + cmdIsoColorMap.strip(',') + ' '
2385  if cmdIsoColorVal:
2386  cmd += "isosurf_color_value=" + cmdIsoColorVal.strip(',') + ' '
2387  if cmdIsoTrMap:
2388  cmd += "isosurf_transparency_map=" + cmdIsoTrMap.strip(',') + ' '
2389  if cmdIsoTrVal:
2390  cmd += "isosurf_transparency_value=" + cmdIsoTrVal.strip(',') + ' '
2391  if cmdSlice:
2392  cmd += "slice=" + cmdSlice.strip(',') + ' '
2393  cmd += "slice_position=" + cmdSlicePos.strip(',') + ' '
2394  cmd += "slice_transparency=" + cmdSliceTransp.strip(',') + ' '
2395 
2396  #
2397  # cutting planes
2398  #
2399  cplane = self.lmgr.nviz.FindWindowById(self.lmgr.nviz.win['cplane']['planes']).GetStringSelection()
2400  try:
2401  planeIndex = int(cplane.split()[-1]) - 1
2402  except (IndexError, ValueError):
2403  planeIndex = None
2404  if planeIndex is not None:
2405  shading = ['clear', 'top', 'bottom', 'blend', 'shaded']
2406  cmd += "cplane=%d " % planeIndex
2407  cmd += "cplane_rotation=%d " % self.cplanes[planeIndex]['rotation']['rot']
2408  cmd += "cplane_tilt=%d " % self.cplanes[planeIndex]['rotation']['tilt']
2409  cmd += "cplane_position=%d,%d,%d " % (self.cplanes[planeIndex]['position']['x'],
2410  self.cplanes[planeIndex]['position']['y'],
2411  self.cplanes[planeIndex]['position']['z'])
2412  cmd += "cplane_shading=%s " % shading[self.cplanes[planeIndex]['shading']]
2413  cmd += "\\\n"
2414  #
2415  # viewpoint
2416  #
2417  subcmd = "position=%.2f,%.2f " % (self.view['position']['x'], self.view['position']['y'])
2418  subcmd += "height=%d " % (self.iview['height']['value'])
2419  subcmd += "perspective=%d " % (self.view['persp']['value'])
2420  subcmd += "twist=%d " % (self.view['twist']['value'])
2421  subcmd += "zexag=%f " % (self.view['z-exag']['value'] / self.iview['z-exag']['llRatio'])
2422  subcmd += "focus=%d,%d,%d " % (self.iview['focus']['x'],self.iview['focus']['y'],self.iview['focus']['z'])
2423  cmd += subcmd
2424 
2425  # background
2426  subcmd = "bgcolor=%d:%d:%d " % (self.view['background']['color'][:3])
2427  if self.view['background']['color'] != (255, 255, 255):
2428  cmd += subcmd
2429  cmd += "\\\n"
2430  # light
2431  subcmd = "light_position=%.2f,%.2f,%.2f " % (self.light['position']['x'],
2432  self.light['position']['y'],
2433  self.light['position']['z']/100.)
2434  subcmd += "light_brightness=%d " % (self.light['bright'])
2435  subcmd += "light_ambient=%d " % (self.light['ambient'])
2436  subcmd += "light_color=%d:%d:%d " % (self.light['color'][:3])
2437  cmd += subcmd
2438  cmd += "\\\n"
2439  # fringe
2440  toolWindow = self.lmgr.nviz
2441  direction = ''
2442  for dir in ('nw', 'ne', 'sw', 'se'):
2443  if toolWindow.FindWindowById(toolWindow.win['fringe'][dir]).IsChecked():
2444  direction += "%s," % dir
2445  if direction:
2446  subcmd = "fringe=%s " % (direction.strip(','))
2447  color = toolWindow.FindWindowById(toolWindow.win['fringe']['color']).GetValue()
2448  subcmd += "fringe_color=%d:%d:%d " % (color[0], color[1], color[2])
2449  subcmd += "fringe_elevation=%d " % (toolWindow.FindWindowById(toolWindow.win['fringe']['elev']).GetValue())
2450  cmd += subcmd
2451  cmd += "\\\n"
2452  # north arrow
2453  if self.decoration['arrow']['show']:
2454  subcmd = "arrow_position=%d,%d " % (self.decoration['arrow']['position']['x'],
2455  self.decoration['arrow']['position']['y'])
2456  subcmd += "arrow_color=%s " % self.decoration['arrow']['color']
2457  subcmd += "arrow_size=%d " % self.decoration['arrow']['size']
2458  cmd += subcmd
2459 
2460  # output
2461  subcmd = 'output=nviz_output '
2462  subcmd += 'format=ppm '
2463  subcmd += 'size=%d,%d ' % self.GetClientSizeTuple()
2464  cmd += subcmd
2465 
2466  return cmd
2467 
2468  def OnNvizCmd(self):
2469  """!Generate and write command to command output"""
2470  self.log.WriteLog(self.NvizCmdCommand(), switchPage = True)
2471 
2472  def SaveToFile(self, FileName, FileType, width, height):
2473  """!This draws the DC to a buffer that can be saved to a file.
2474 
2475  @todo fix BufferedPaintDC
2476 
2477  @param FileName file name
2478  @param FileType type of bitmap
2479  @param width image width
2480  @param height image height
2481  """
2482  self._display.SaveToFile(FileName, width, height, FileType)
2483 
2484  # pbuffer = wx.EmptyBitmap(max(1, self.Map.width), max(1, self.Map.height))
2485  # dc = wx.BufferedPaintDC(self, pbuffer)
2486  # dc.Clear()
2487  # self.SetCurrent()
2488  # self._display.Draw(False, -1)
2489  # pbuffer.SaveFile(FileName, FileType)
2490  # self.SwapBuffers()
2491 
2492  def GetDisplay(self):
2493  """!Get display instance"""
2494  return self._display
2495 
2496  def ZoomToMap(self):
2497  """!Reset view
2498  """
2499  self.lmgr.nviz.OnResetView(None)
2500 
2501  def TextBounds(self, textinfo):
2502  """!Return text boundary data
2503 
2504  @param textinfo text metadata (text, font, color, rotation)
2505  """
2506  return self.parent.MapWindow2D.TextBounds(textinfo, relcoords = True)