GRASS Programmer's Manual  6.4.2(2012)
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
mapdisp_window.py
Go to the documentation of this file.
1 """!
2 @package mapdisp_window.py
3 
4 @brief Map display canvas - buffered window.
5 
6 Classes:
7  - MapWindow
8  - BufferedWindow
9 
10 (C) 2006-2011 by the GRASS Development Team
11 This program is free software under the GNU General Public
12 License (>=v2). Read the file COPYING that comes with GRASS
13 for details.
14 
15 @author Martin Landa <landa.martin gmail.com>
16 @author Michael Barton
17 @author Jachym Cepicky
18 """
19 
20 import os
21 import time
22 import math
23 import sys
24 import tempfile
25 import traceback
26 
27 import wx
28 
29 import grass.script as grass
30 
31 import dbm
32 import gdialogs
33 import gcmd
34 import utils
35 import globalvar
36 import gselect
37 from debug import Debug
38 from preferences import globalSettings as UserSettings
39 from units import ConvertValue as UnitsConvertValue
40 
41 try:
42  import grass.lib.gis as gislib
43  haveCtypes = True
44 except ImportError:
45  haveCtypes = False
46 
47 class MapWindow(object):
48  """!Abstract map display window class
49 
50  Superclass for BufferedWindow class (2D display mode), and GLWindow
51  (3D display mode).
52  """
53  def __init__(self, parent, id = wx.ID_ANY,
54  Map = None, tree = None, lmgr = None, **kwargs):
55  self.parent = parent # MapFrame
56  self.Map = Map
57  self.tree = tree
58  self.lmgr = lmgr
59 
60  # mouse attributes -- position on the screen, begin and end of
61  # dragging, and type of drawing
62  self.mouse = {
63  'begin': [0, 0], # screen coordinates
64  'end' : [0, 0],
65  'use' : "pointer",
66  'box' : "point"
67  }
68 
69  def OnMotion(self, event):
70  """!Track mouse motion and update statusbar
71  """
72  if self.parent.statusbarWin['toggle'].GetSelection() == 0: # Coordinates
73  precision = int(UserSettings.Get(group = 'projection', key = 'format',
74  subkey = 'precision'))
75  format = UserSettings.Get(group = 'projection', key = 'format',
76  subkey = 'll')
77  try:
78  e, n = self.Pixel2Cell(event.GetPositionTuple())
79  except (TypeError, ValueError):
80  self.parent.statusbar.SetStatusText("", 0)
81  return
82 
83  updated = False
84  if hasattr(self, "digit"):
85  updated = self._onMotion((e, n), precision)
86 
87  if not updated:
88  if self.parent.statusbarWin['projection'].IsChecked():
89  if not UserSettings.Get(group = 'projection', key = 'statusbar', subkey = 'proj4'):
90  self.parent.statusbar.SetStatusText(_("Projection not defined (check the settings)"), 0)
91  else:
92  proj, coord = utils.ReprojectCoordinates(coord = (e, n),
93  projOut = UserSettings.Get(group = 'projection',
94  key = 'statusbar',
95  subkey = 'proj4'),
96  flags = 'd')
97 
98  if coord:
99  e, n = coord
100  if proj in ('ll', 'latlong', 'longlat') and format == 'DMS':
101  self.parent.statusbar.SetStatusText(utils.Deg2DMS(e, n, precision = precision),
102  0)
103  else:
104  self.parent.statusbar.SetStatusText("%.*f; %.*f" % \
105  (precision, e, precision, n), 0)
106  else:
107  self.parent.statusbar.SetStatusText(_("Error in projection (check the settings)"), 0)
108  else:
109  if self.parent.Map.projinfo['proj'] == 'll' and format == 'DMS':
110  self.parent.statusbar.SetStatusText(utils.Deg2DMS(e, n, precision = precision),
111  0)
112  else:
113  self.parent.statusbar.SetStatusText("%.*f; %.*f" % \
114  (precision, e, precision, n), 0)
115 
116  event.Skip()
117 
118  def GetLayerByName(self, name, mapType, dataType = 'layer'):
119  """!Get layer from layer tree by nam
120 
121  @param name layer name
122  @param type 'item' / 'layer' / 'nviz'
123 
124  @return layer / map layer properties / nviz properties
125  @return None
126  """
127  if not self.tree:
128  return None
129 
130  try:
131  mapLayer = self.Map.GetListOfLayers(l_type = mapType, l_name = name)[0]
132  except IndexError:
133  return None
134 
135  if dataType == 'layer':
136  return mapLayer
137  item = self.tree.FindItemByData('maplayer', mapLayer)
138  if not item:
139  return None
140  if dataType == 'nviz':
141  return self.tree.GetPyData(item)[0]['nviz']
142 
143  return item
144 
145  def GetSelectedLayer(self, type = 'layer', multi = False):
146  """!Get selected layer from layer tree
147 
148  @param type 'item' / 'layer' / 'nviz'
149  @param multi return first selected layer or all
150 
151  @return layer / map layer properties / nviz properties
152  @return None / [] on failure
153  """
154  ret = []
155  if not self.tree or \
156  not self.tree.GetSelection():
157  if multi:
158  return []
159  else:
160  return None
161 
162  if multi and \
163  type == 'item':
164  return self.tree.GetSelections()
165 
166  for item in self.tree.GetSelections():
167  if not item.IsChecked():
168  if multi:
169  continue
170  else:
171  return None
172 
173  if type == 'item': # -> multi = False
174  return item
175 
176  try:
177  if type == 'nviz':
178  layer = self.tree.GetPyData(item)[0]['nviz']
179  else:
180  layer = self.tree.GetPyData(item)[0]['maplayer']
181  except:
182  layer = None
183 
184  if multi:
185  ret.append(layer)
186  else:
187  return layer
188 
189  return ret
190 
191 class BufferedWindow(MapWindow, wx.Window):
192  """!A Buffered window class (2D view mode)
193 
194  Superclass for VDigitWindow (vector digitizer).
195 
196  When the drawing needs to change, you app needs to call the
197  UpdateMap() method. Since the drawing is stored in a bitmap, you
198  can also save the drawing to file by calling the
199  SaveToFile() method.
200  """
201  def __init__(self, parent, id = wx.ID_ANY,
202  Map = None, tree = None, lmgr = None,
203  style = wx.NO_FULL_REPAINT_ON_RESIZE, **kwargs):
204  MapWindow.__init__(self, parent, id, Map, tree, lmgr, **kwargs)
205  wx.Window.__init__(self, parent, id, style = style, **kwargs)
206 
207  # flags
208  self.resize = False # indicates whether or not a resize event has taken place
209  self.dragimg = None # initialize variable for map panning
210 
211  # variables for drawing on DC
212  self.pen = None # pen for drawing zoom boxes, etc.
213  self.polypen = None # pen for drawing polylines (measurements, profiles, etc)
214  # List of wx.Point tuples defining a polyline (geographical coordinates)
215  self.polycoords = []
216  # ID of rubber band line
217  self.lineid = None
218  # ID of poly line resulting from cumulative rubber band lines (e.g. measurement)
219  self.plineid = None
220 
221  # event bindings
222  self.Bind(wx.EVT_PAINT, self.OnPaint)
223  self.Bind(wx.EVT_SIZE, self.OnSize)
224  self.Bind(wx.EVT_IDLE, self.OnIdle)
225  self.Bind(wx.EVT_MOUSE_EVENTS, self.MouseActions)
226  self.Bind(wx.EVT_MOTION, self.OnMotion)
227 
228  self.processMouse = True
229 
230  # render output objects
231  self.mapfile = None # image file to be rendered
232  self.img = None # wx.Image object (self.mapfile)
233  # decoration overlays
234  self.overlays = {}
235  # images and their PseudoDC ID's for painting and dragging
236  self.imagedict = {}
237  self.select = {} # selecting/unselecting decorations for dragging
238  self.textdict = {} # text, font, and color indexed by id
239  self.currtxtid = None # PseudoDC id for currently selected text
240 
241  # zoom objects
242  self.zoomhistory = [] # list of past zoom extents
243  self.currzoom = 0 # current set of extents in zoom history being used
244  self.zoomtype = 1 # 1 zoom in, 0 no zoom, -1 zoom out
245  self.hitradius = 10 # distance for selecting map decorations
246  self.dialogOffset = 5 # offset for dialog (e.g. DisplayAttributesDialog)
247 
248  # OnSize called to make sure the buffer is initialized.
249  # This might result in OnSize getting called twice on some
250  # platforms at initialization, but little harm done.
251  ### self.OnSize(None)
252 
253  self._definePseudoDC()
254  # redraw all pdc's, pdcTmp layer is redrawn always (speed issue)
255  self.redrawAll = True
256 
257  # will store an off screen empty bitmap for saving to file
258  self._buffer = None
259 
260  self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x:None)
261 
262  # vars for handling mouse clicks
263  self.dragid = -1
264  self.lastpos = (0, 0)
265 
266  def _definePseudoDC(self):
267  """!Define PseudoDC objects to use
268  """
269  # create PseudoDC used for background map, map decorations like scales and legends
270  self.pdc = wx.PseudoDC()
271  # used for digitization tool
272  self.pdcVector = None
273  # decorations (region box, etc.)
274  self.pdcDec = wx.PseudoDC()
275  # pseudoDC for temporal objects (select box, measurement tool, etc.)
276  self.pdcTmp = wx.PseudoDC()
277 
278  def Draw(self, pdc, img = None, drawid = None, pdctype = 'image', coords = [0, 0, 0, 0]):
279  """!Draws map and overlay decorations
280  """
281  if drawid == None:
282  if pdctype == 'image' and img:
283  drawid = self.imagedict[img]
284  elif pdctype == 'clear':
285  drawid == None
286  else:
287  drawid = wx.NewId()
288 
289  if img and pdctype == 'image':
290  # self.imagedict[img]['coords'] = coords
291  self.select[self.imagedict[img]['id']] = False # ?
292 
293  pdc.BeginDrawing()
294 
295  if drawid != 99:
296  bg = wx.TRANSPARENT_BRUSH
297  else:
298  bg = wx.Brush(self.GetBackgroundColour())
299 
300  pdc.SetBackground(bg)
301 
302  Debug.msg (5, "BufferedWindow.Draw(): id=%s, pdctype = %s, coord=%s" % \
303  (drawid, pdctype, coords))
304 
305  # set PseudoDC id
306  if drawid is not None:
307  pdc.SetId(drawid)
308 
309  if pdctype == 'clear': # erase the display
310  bg = wx.WHITE_BRUSH
311  # bg = wx.Brush(self.GetBackgroundColour())
312  pdc.SetBackground(bg)
313  pdc.RemoveAll()
314  pdc.Clear()
315  pdc.EndDrawing()
316 
317  self.Refresh()
318  return
319 
320  if pdctype == 'image': # draw selected image
321  bitmap = wx.BitmapFromImage(img)
322  w,h = bitmap.GetSize()
323  pdc.DrawBitmap(bitmap, coords[0], coords[1], True) # draw the composite map
324  pdc.SetIdBounds(drawid, wx.Rect(coords[0],coords[1], w, h))
325 
326  elif pdctype == 'box': # draw a box on top of the map
327  if self.pen:
328  pdc.SetBrush(wx.Brush(wx.CYAN, wx.TRANSPARENT))
329  pdc.SetPen(self.pen)
330  x2 = max(coords[0],coords[2])
331  x1 = min(coords[0],coords[2])
332  y2 = max(coords[1],coords[3])
333  y1 = min(coords[1],coords[3])
334  rwidth = x2-x1
335  rheight = y2-y1
336  rect = wx.Rect(x1, y1, rwidth, rheight)
337  pdc.DrawRectangleRect(rect)
338  pdc.SetIdBounds(drawid, rect)
339 
340  elif pdctype == 'line': # draw a line on top of the map
341  if self.pen:
342  pdc.SetBrush(wx.Brush(wx.CYAN, wx.TRANSPARENT))
343  pdc.SetPen(self.pen)
344  pdc.DrawLinePoint(wx.Point(coords[0], coords[1]),wx.Point(coords[2], coords[3]))
345  pdc.SetIdBounds(drawid, wx.Rect(coords[0], coords[1], coords[2], coords[3]))
346 
347  elif pdctype == 'polyline': # draw a polyline on top of the map
348  if self.polypen:
349  pdc.SetBrush(wx.Brush(wx.CYAN, wx.TRANSPARENT))
350  pdc.SetPen(self.polypen)
351  if (len(coords) < 2):
352  return
353  i = 1
354  while i < len(coords):
355  pdc.DrawLinePoint(wx.Point(coords[i-1][0], coords[i-1][1]),
356  wx.Point(coords[i][0], coords[i][1]))
357  i += 1
358 
359  # get bounding rectangle for polyline
360  xlist = []
361  ylist = []
362  if len(coords) > 0:
363  for point in coords:
364  x,y = point
365  xlist.append(x)
366  ylist.append(y)
367  x1 = min(xlist)
368  x2 = max(xlist)
369  y1 = min(ylist)
370  y2 = max(ylist)
371  pdc.SetIdBounds(drawid, wx.Rect(x1,y1,x2,y2))
372  # self.ovlcoords[drawid] = [x1,y1,x2,y2]
373 
374  elif pdctype == 'point': # draw point
375  if self.pen:
376  pdc.SetPen(self.pen)
377  pdc.DrawPoint(coords[0], coords[1])
378  coordsBound = (coords[0] - 5,
379  coords[1] - 5,
380  coords[0] + 5,
381  coords[1] + 5)
382  pdc.SetIdBounds(drawid, wx.Rect(coordsBound))
383 
384  elif pdctype == 'text': # draw text on top of map
385  if not img['active']:
386  return # only draw active text
387  if 'rotation' in img:
388  rotation = float(img['rotation'])
389  else:
390  rotation = 0.0
391  w, h = self.GetFullTextExtent(img['text'])[0:2]
392  pdc.SetFont(img['font'])
393  pdc.SetTextForeground(img['color'])
394  coords, w, h = self.TextBounds(img)
395  if rotation == 0:
396  pdc.DrawText(img['text'], coords[0], coords[1])
397  else:
398  pdc.DrawRotatedText(img['text'], coords[0], coords[1], rotation)
399  pdc.SetIdBounds(drawid, wx.Rect(coords[0], coords[1], w, h))
400 
401  pdc.EndDrawing()
402 
403  self.Refresh()
404 
405  return drawid
406 
407  def TextBounds(self, textinfo):
408  """!Return text boundary data
409 
410  @param textinfo text metadata (text, font, color, rotation)
411  @param coords reference point
412  """
413  if 'rotation' in textinfo:
414  rotation = float(textinfo['rotation'])
415  else:
416  rotation = 0.0
417 
418  coords = textinfo['coords']
419 
420  Debug.msg (4, "BufferedWindow.TextBounds(): text=%s, rotation=%f" % \
421  (textinfo['text'], rotation))
422 
423  self.Update()
424 
425  self.SetFont(textinfo['font'])
426 
427  w, h = self.GetTextExtent(textinfo['text'])
428 
429  if rotation == 0:
430  coords[2], coords[3] = coords[0] + w, coords[1] + h
431  return coords, w, h
432 
433  boxh = math.fabs(math.sin(math.radians(rotation)) * w) + h
434  boxw = math.fabs(math.cos(math.radians(rotation)) * w) + h
435  coords[2] = coords[0] + boxw
436  coords[3] = coords[1] + boxh
437 
438  return coords, boxw, boxh
439 
440  def OnPaint(self, event):
441  """!Draw PseudoDC's to buffered paint DC
442 
443  If self.redrawAll is False on self.pdcTmp content is re-drawn
444  """
445  Debug.msg(4, "BufferedWindow.OnPaint(): redrawAll=%s" % self.redrawAll)
446 
447  dc = wx.BufferedPaintDC(self, self.buffer)
448  dc.Clear()
449 
450  # use PrepareDC to set position correctly
451  self.PrepareDC(dc)
452 
453  # create a clipping rect from our position and size
454  # and update region
455  rgn = self.GetUpdateRegion().GetBox()
456  dc.SetClippingRect(rgn)
457 
458  switchDraw = False
459  if self.redrawAll is None:
460  self.redrawAll = True
461  switchDraw = True
462 
463  if self.redrawAll: # redraw pdc and pdcVector
464  # draw to the dc using the calculated clipping rect
465  self.pdc.DrawToDCClipped(dc, rgn)
466 
467  # draw vector map layer
468  if hasattr(self, "digit"):
469  # decorate with GDDC (transparency)
470  try:
471  gcdc = wx.GCDC(dc)
472  self.pdcVector.DrawToDCClipped(gcdc, rgn)
473  except NotImplementedError, e:
474  print >> sys.stderr, e
475  self.pdcVector.DrawToDCClipped(dc, rgn)
476 
477  self.bufferLast = None
478  else: # do not redraw pdc and pdcVector
479  if self.bufferLast is None:
480  # draw to the dc
481  self.pdc.DrawToDC(dc)
482 
483  if hasattr(self, "digit"):
484  # decorate with GDDC (transparency)
485  try:
486  gcdc = wx.GCDC(dc)
487  self.pdcVector.DrawToDC(gcdc)
488  except NotImplementedError, e:
489  print >> sys.stderr, e
490  self.pdcVector.DrawToDC(dc)
491 
492  # store buffered image
493  # self.bufferLast = wx.BitmapFromImage(self.buffer.ConvertToImage())
494  self.bufferLast = dc.GetAsBitmap(wx.Rect(0, 0, self.Map.width, self.Map.height))
495 
496  self.pdc.DrawBitmap(self.bufferLast, 0, 0, False)
497  self.pdc.DrawToDC(dc)
498 
499  # draw decorations (e.g. region box)
500  try:
501  gcdc = wx.GCDC(dc)
502  self.pdcDec.DrawToDC(gcdc)
503  except NotImplementedError, e:
504  print >> sys.stderr, e
505  self.pdcDec.DrawToDC(dc)
506 
507  # draw temporary object on the foreground
508  ### self.pdcTmp.DrawToDCClipped(dc, rgn)
509  self.pdcTmp.DrawToDC(dc)
510 
511  if switchDraw:
512  self.redrawAll = False
513 
514  def OnSize(self, event):
515  """!Scale map image so that it is the same size as the Window
516  """
517  Debug.msg(3, "BufferedWindow.OnSize():")
518 
519  # set size of the input image
520  self.Map.ChangeMapSize(self.GetClientSize())
521  # align extent based on center point and display resolution
522  # this causes that image is not resized when display windows is resized
523  ### self.Map.AlignExtentFromDisplay()
524 
525  # Make new off screen bitmap: this bitmap will always have the
526  # current drawing in it, so it can be used to save the image to
527  # a file, or whatever.
528  self.buffer = wx.EmptyBitmap(max(1, self.Map.width), max(1, self.Map.height))
529 
530  # get the image to be rendered
531  self.img = self.GetImage()
532 
533  # update map display
534  if self.img and self.Map.width + self.Map.height > 0: # scale image during resize
535  self.img = self.img.Scale(self.Map.width, self.Map.height)
536  if len(self.Map.GetListOfLayers()) > 0:
537  self.UpdateMap()
538 
539  # re-render image on idle
540  self.resize = True
541 
542  # reposition checkbox in statusbar
543  self.parent.StatusbarReposition()
544 
545  # update statusbar
546  self.parent.StatusbarUpdate()
547 
548  def OnIdle(self, event):
549  """!Only re-render a composite map image from GRASS during
550  idle time instead of multiple times during resizing.
551  """
552  if self.resize:
553  self.UpdateMap(render = True)
554 
555  event.Skip()
556 
557  def SaveToFile(self, FileName, FileType, width, height):
558  """!This draws the pseudo DC to a buffer that can be saved to
559  a file.
560 
561  @param FileName file name
562  @param FileType type of bitmap
563  @param width image width
564  @param height image height
565  """
566  busy = wx.BusyInfo(message = _("Please wait, exporting image..."),
567  parent = self)
568  wx.Yield()
569 
570  self.Map.ChangeMapSize((width, height))
571  ibuffer = wx.EmptyBitmap(max(1, width), max(1, height))
572  self.Map.Render(force = True, windres = True)
573  img = self.GetImage()
574  self.Draw(self.pdc, img, drawid = 99)
575  dc = wx.BufferedPaintDC(self, ibuffer)
576  dc.Clear()
577  self.PrepareDC(dc)
578  self.pdc.DrawToDC(dc)
579  if self.pdcVector:
580  self.pdcVector.DrawToDC(dc)
581  ibuffer.SaveFile(FileName, FileType)
582 
583  busy.Destroy()
584 
585  self.UpdateMap(render = True)
586  self.Refresh()
587 
588  def GetOverlay(self):
589  """!Converts rendered overlay files to wx.Image
590 
591  Updates self.imagedict
592 
593  @return list of images
594  """
595  imgs = []
596  for overlay in self.Map.GetListOfLayers(l_type = "overlay", l_active = True):
597  if os.path.isfile(overlay.mapfile) and os.path.getsize(overlay.mapfile):
598  img = wx.Image(overlay.mapfile, wx.BITMAP_TYPE_ANY)
599  self.imagedict[img] = { 'id' : overlay.id,
600  'layer' : overlay }
601  imgs.append(img)
602 
603  return imgs
604 
605  def GetImage(self):
606  """!Converts redered map files to wx.Image
607 
608  Updates self.imagedict (id=99)
609 
610  @return wx.Image instance (map composition)
611  """
612  imgId = 99
613  if self.mapfile and self.Map.mapfile and os.path.isfile(self.Map.mapfile) and \
614  os.path.getsize(self.Map.mapfile):
615  img = wx.Image(self.Map.mapfile, wx.BITMAP_TYPE_ANY)
616  else:
617  img = None
618 
619  self.imagedict[img] = { 'id': imgId }
620 
621  return img
622 
623  def UpdateMap(self, render = True, renderVector = True):
624  """!Updates the canvas anytime there is a change to the
625  underlaying images or to the geometry of the canvas.
626 
627  @param render re-render map composition
628  @param renderVector re-render vector map layer enabled for editing (used for digitizer)
629  """
630  start = time.clock()
631 
632  self.resize = False
633 
634  if self.img is None:
635  render = True
636 
637  #
638  # initialize process bar (only on 'render')
639  #
640  if render or renderVector:
641  self.parent.statusbarWin['progress'].Show()
642  if self.parent.statusbarWin['progress'].GetRange() > 0:
643  self.parent.statusbarWin['progress'].SetValue(1)
644 
645  #
646  # render background image if needed
647  #
648  # update layer dictionary if there has been a change in layers
649  if self.tree and self.tree.reorder:
650  self.tree.ReorderLayers()
651 
652  # reset flag for auto-rendering
653  if self.tree:
654  self.tree.rerender = False
655 
656  try:
657  if render:
658  # update display size
659  self.Map.ChangeMapSize(self.GetClientSize())
660  if self.parent.statusbarWin['resolution'].IsChecked():
661  # use computation region resolution for rendering
662  windres = True
663  else:
664  windres = False
665  self.mapfile = self.Map.Render(force = True, mapWindow = self.parent,
666  windres = windres)
667  else:
668  self.mapfile = self.Map.Render(force = False, mapWindow = self.parent)
669  except gcmd.GException, e:
670  gcmd.GError(message = e.value)
671  self.mapfile = None
672 
673  self.img = self.GetImage() # id=99
674 
675  #
676  # clear pseudoDcs
677  #
678  for pdc in (self.pdc,
679  self.pdcDec,
680  self.pdcTmp):
681  pdc.Clear()
682  pdc.RemoveAll()
683 
684  #
685  # draw background map image to PseudoDC
686  #
687  if not self.img:
688  self.Draw(self.pdc, pdctype = 'clear')
689  else:
690  try:
691  id = self.imagedict[self.img]['id']
692  except:
693  return False
694 
695  self.Draw(self.pdc, self.img, drawid = id)
696 
697  #
698  # render vector map layer
699  #
700  if renderVector and hasattr(self, "digit"):
701  self._updateMap()
702  #
703  # render overlays
704  #
705  for img in self.GetOverlay():
706  # draw any active and defined overlays
707  if self.imagedict[img]['layer'].IsActive():
708  id = self.imagedict[img]['id']
709  self.Draw(self.pdc, img = img, drawid = id,
710  pdctype = self.overlays[id]['pdcType'], coords = self.overlays[id]['coords'])
711 
712  for id in self.textdict.keys():
713  self.Draw(self.pdc, img = self.textdict[id], drawid = id,
714  pdctype = 'text', coords = [10, 10, 10, 10])
715 
716  # optionally draw computational extent box
717  self.DrawCompRegionExtent()
718 
719  #
720  # redraw pdcTmp if needed
721  #
722  if len(self.polycoords) > 0:
723  self.DrawLines(self.pdcTmp)
724 
725  if not self.parent.IsStandalone() and \
726  self.parent.GetLayerManager().georectifying:
727  # -> georectifier (redraw GCPs)
728  if self.parent.toolbars['georect']:
729  coordtype = 'gcpcoord'
730  else:
731  coordtype = 'mapcoord'
732  self.parent.GetLayerManager().georectifying.DrawGCP(coordtype)
733 
734  if not self.parent.IsStandalone() and \
735  self.parent.GetLayerManager().gcpmanagement:
736  # -> georectifier (redraw GCPs)
737  if self.parent.toolbars['gcpdisp']:
738  if self == self.parent.TgtMapWindow:
739  coordtype = 'target'
740  else:
741  coordtype = 'source'
742 
743  self.parent.DrawGCP(coordtype)
744 
745  #
746  # clear measurement
747  #
748  if self.mouse["use"] == "measure":
749  self.ClearLines(pdc = self.pdcTmp)
750  self.polycoords = []
751  self.mouse['use'] = 'pointer'
752  self.mouse['box'] = 'point'
753  self.mouse['end'] = [0, 0]
754  self.SetCursor(self.parent.cursors["default"])
755 
756  stop = time.clock()
757 
758  #
759  # hide process bar
760  #
761  self.parent.statusbarWin['progress'].Hide()
762 
763  #
764  # update statusbar
765  #
766  ### self.Map.SetRegion()
767  self.parent.StatusbarUpdate()
768  if grass.find_file(name = 'MASK', element = 'cell')['name']:
769  # mask found
770  self.parent.statusbarWin['mask'].SetLabel(_('MASK'))
771  else:
772  self.parent.statusbarWin['mask'].SetLabel('')
773 
774  Debug.msg (1, "BufferedWindow.UpdateMap(): render=%s, renderVector=%s -> time=%g" % \
775  (render, renderVector, (stop-start)))
776 
777  return True
778 
780  """!Draw computational region extent in the display
781 
782  Display region is drawn as a blue box inside the computational region,
783  computational region inside a display region as a red box).
784  """
785  if hasattr(self, "regionCoords"):
786  compReg = self.Map.GetRegion()
787  dispReg = self.Map.GetCurrentRegion()
788  reg = None
789  if self.IsInRegion(dispReg, compReg):
790  self.polypen = wx.Pen(colour = wx.Colour(0, 0, 255, 128), width = 3, style = wx.SOLID)
791  reg = dispReg
792  else:
793  self.polypen = wx.Pen(colour = wx.Colour(255, 0, 0, 128),
794  width = 3, style = wx.SOLID)
795  reg = compReg
796 
797  self.regionCoords = []
798  self.regionCoords.append((reg['w'], reg['n']))
799  self.regionCoords.append((reg['e'], reg['n']))
800  self.regionCoords.append((reg['e'], reg['s']))
801  self.regionCoords.append((reg['w'], reg['s']))
802  self.regionCoords.append((reg['w'], reg['n']))
803  # draw region extent
804  self.DrawLines(pdc = self.pdcDec, polycoords = self.regionCoords)
805 
806  def IsInRegion(self, region, refRegion):
807  """!
808  Test if 'region' is inside of 'refRegion'
809 
810  @param region input region
811  @param refRegion reference region (e.g. computational region)
812 
813  @return True if region is inside of refRegion
814  @return False
815  """
816  if region['s'] >= refRegion['s'] and \
817  region['n'] <= refRegion['n'] and \
818  region['w'] >= refRegion['w'] and \
819  region['e'] <= refRegion['e']:
820  return True
821 
822  return False
823 
824  def EraseMap(self):
825  """!Erase map canvas
826  """
827  self.Draw(self.pdc, pdctype = 'clear')
828 
829  if hasattr(self, "digit"):
830  self.Draw(self.pdcVector, pdctype = 'clear')
831 
832  self.Draw(self.pdcDec, pdctype = 'clear')
833  self.Draw(self.pdcTmp, pdctype = 'clear')
834 
835  def DragMap(self, moveto):
836  """!Drag the entire map image for panning.
837 
838  @param moveto dx,dy
839  """
840  dc = wx.BufferedDC(wx.ClientDC(self))
841  dc.SetBackground(wx.Brush("White"))
842  dc.Clear()
843 
844  self.dragimg = wx.DragImage(self.buffer)
845  self.dragimg.BeginDrag((0, 0), self)
846  self.dragimg.GetImageRect(moveto)
847  self.dragimg.Move(moveto)
848 
849  self.dragimg.DoDrawImage(dc, moveto)
850  self.dragimg.EndDrag()
851 
852  def DragItem(self, id, event):
853  """!Drag an overlay decoration item
854  """
855  if id == 99 or id == '' or id == None: return
856  Debug.msg (5, "BufferedWindow.DragItem(): id=%d" % id)
857  x, y = self.lastpos
858  dx = event.GetX() - x
859  dy = event.GetY() - y
860  self.pdc.SetBackground(wx.Brush(self.GetBackgroundColour()))
861  r = self.pdc.GetIdBounds(id)
862  if type(r) is list:
863  r = wx.Rect(r[0], r[1], r[2], r[3])
864  if id > 100: # text dragging
865  rtop = (r[0],r[1]-r[3],r[2],r[3])
866  r = r.Union(rtop)
867  rleft = (r[0]-r[2],r[1],r[2],r[3])
868  r = r.Union(rleft)
869  self.pdc.TranslateId(id, dx, dy)
870 
871  r2 = self.pdc.GetIdBounds(id)
872  if type(r2) is list:
873  r2 = wx.Rect(r[0], r[1], r[2], r[3])
874  if id > 100: # text
875  self.textdict[id]['coords'] = r2
876  r = r.Union(r2)
877  r.Inflate(4,4)
878  self.RefreshRect(r, False)
879  self.lastpos = (event.GetX(), event.GetY())
880 
881  def MouseDraw(self, pdc = None, begin = None, end = None):
882  """!Mouse box or line from 'begin' to 'end'
883 
884  If not given from self.mouse['begin'] to self.mouse['end'].
885  """
886  if not pdc:
887  return
888 
889  if begin is None:
890  begin = self.mouse['begin']
891  if end is None:
892  end = self.mouse['end']
893 
894  Debug.msg (5, "BufferedWindow.MouseDraw(): use=%s, box=%s, begin=%f,%f, end=%f,%f" % \
895  (self.mouse['use'], self.mouse['box'],
896  begin[0], begin[1], end[0], end[1]))
897 
898  if self.mouse['box'] == "box":
899  boxid = wx.ID_NEW
900  mousecoords = [begin[0], begin[1],
901  end[0], end[1]]
902  r = pdc.GetIdBounds(boxid)
903  if type(r) is list:
904  r = wx.Rect(r[0], r[1], r[2], r[3])
905  r.Inflate(4, 4)
906  try:
907  pdc.ClearId(boxid)
908  except:
909  pass
910  self.RefreshRect(r, False)
911  pdc.SetId(boxid)
912  self.Draw(pdc, drawid = boxid, pdctype = 'box', coords = mousecoords)
913 
914  elif self.mouse['box'] == "line":
915  self.lineid = wx.ID_NEW
916  mousecoords = [begin[0], begin[1], \
917  end[0], end[1]]
918  x1 = min(begin[0],end[0])
919  x2 = max(begin[0],end[0])
920  y1 = min(begin[1],end[1])
921  y2 = max(begin[1],end[1])
922  r = wx.Rect(x1,y1,x2-x1,y2-y1)
923  r.Inflate(4,4)
924  try:
925  pdc.ClearId(self.lineid)
926  except:
927  pass
928  self.RefreshRect(r, False)
929  pdc.SetId(self.lineid)
930  self.Draw(pdc, drawid = self.lineid, pdctype = 'line', coords = mousecoords)
931 
932  def DrawLines(self, pdc = None, polycoords = None):
933  """!Draw polyline in PseudoDC
934 
935  Set self.pline to wx.NEW_ID + 1
936 
937  polycoords - list of polyline vertices, geographical coordinates
938  (if not given, self.polycoords is used)
939  """
940  if not pdc:
941  pdc = self.pdcTmp
942 
943  if not polycoords:
944  polycoords = self.polycoords
945 
946  if len(polycoords) > 0:
947  self.plineid = wx.ID_NEW + 1
948  # convert from EN to XY
949  coords = []
950  for p in polycoords:
951  coords.append(self.Cell2Pixel(p))
952 
953  self.Draw(pdc, drawid = self.plineid, pdctype = 'polyline', coords = coords)
954 
955  Debug.msg (4, "BufferedWindow.DrawLines(): coords=%s, id=%s" % \
956  (coords, self.plineid))
957 
958  return self.plineid
959 
960  return -1
961 
962  def DrawCross(self, pdc, coords, size, rotation = 0,
963  text = None, textAlign = 'lr', textOffset = (5, 5)):
964  """!Draw cross in PseudoDC
965 
966  @todo implement rotation
967 
968  @param pdc PseudoDC
969  @param coord center coordinates
970  @param rotation rotate symbol
971  @param text draw also text (text, font, color, rotation)
972  @param textAlign alignment (default 'lower-right')
973  @textOffset offset for text (from center point)
974  """
975  Debug.msg(4, "BufferedWindow.DrawCross(): pdc=%s, coords=%s, size=%d" % \
976  (pdc, coords, size))
977  coordsCross = ((coords[0] - size, coords[1], coords[0] + size, coords[1]),
978  (coords[0], coords[1] - size, coords[0], coords[1] + size))
979 
980  self.lineid = wx.NewId()
981  for lineCoords in coordsCross:
982  self.Draw(pdc, drawid = self.lineid, pdctype = 'line', coords = lineCoords)
983 
984  if not text:
985  return self.lineid
986 
987  if textAlign == 'ul':
988  coord = [coords[0] - textOffset[0], coords[1] - textOffset[1], 0, 0]
989  elif textAlign == 'ur':
990  coord = [coords[0] + textOffset[0], coords[1] - textOffset[1], 0, 0]
991  elif textAlign == 'lr':
992  coord = [coords[0] + textOffset[0], coords[1] + textOffset[1], 0, 0]
993  else:
994  coord = [coords[0] - textOffset[0], coords[1] + textOffset[1], 0, 0]
995 
996  self.Draw(pdc, img = text,
997  pdctype = 'text', coords = coord)
998 
999  return self.lineid
1000 
1001  def MouseActions(self, event):
1002  """!Mouse motion and button click notifier
1003  """
1004  if not self.processMouse:
1005  return
1006 
1007  # zoom with mouse wheel
1008  if event.GetWheelRotation() != 0:
1009  self.OnMouseWheel(event)
1010 
1011  # left mouse button pressed
1012  elif event.LeftDown():
1013  self.OnLeftDown(event)
1014 
1015  # left mouse button released
1016  elif event.LeftUp():
1017  self.OnLeftUp(event)
1018 
1019  # dragging
1020  elif event.Dragging():
1021  self.OnDragging(event)
1022 
1023  # double click
1024  elif event.ButtonDClick():
1025  self.OnButtonDClick(event)
1026 
1027  # middle mouse button pressed
1028  elif event.MiddleDown():
1029  self.OnMiddleDown(event)
1030 
1031  # middle mouse button relesed
1032  elif event.MiddleUp():
1033  self.OnMiddleUp(event)
1034 
1035  # right mouse button pressed
1036  elif event.RightDown():
1037  self.OnRightDown(event)
1038 
1039  # right mouse button released
1040  elif event.RightUp():
1041  self.OnRightUp(event)
1042 
1043  elif event.Entering():
1044  self.OnMouseEnter(event)
1045 
1046  elif event.Moving():
1047  self.OnMouseMoving(event)
1048 
1049  def OnMouseWheel(self, event):
1050  """!Mouse wheel moved
1051  """
1052  self.processMouse = False
1053  current = event.GetPositionTuple()[:]
1054  wheel = event.GetWheelRotation()
1055  Debug.msg (5, "BufferedWindow.MouseAction(): wheel=%d" % wheel)
1056  # zoom 1/2 of the screen, centered to current mouse position (TODO: settings)
1057  begin = (current[0] - self.Map.width / 4,
1058  current[1] - self.Map.height / 4)
1059  end = (current[0] + self.Map.width / 4,
1060  current[1] + self.Map.height / 4)
1061 
1062  if wheel > 0:
1063  zoomtype = 1
1064  else:
1065  zoomtype = -1
1066 
1067  # zoom
1068  self.Zoom(begin, end, zoomtype)
1069 
1070  # redraw map
1071  self.UpdateMap()
1072 
1073  # update statusbar
1074  self.parent.StatusbarUpdate()
1075 
1076  self.Refresh()
1077  self.processMouse = True
1078 
1079  def OnDragging(self, event):
1080  """!Mouse dragging
1081  """
1082  Debug.msg (5, "BufferedWindow.MouseAction(): Dragging")
1083  current = event.GetPositionTuple()[:]
1084  previous = self.mouse['begin']
1085  move = (current[0] - previous[0],
1086  current[1] - previous[1])
1087 
1088  if hasattr(self, "digit"):
1089  digitToolbar = self.toolbar
1090  else:
1091  digitToolbar = None
1092 
1093  # dragging or drawing box with left button
1094  if self.mouse['use'] == 'pan' or \
1095  event.MiddleIsDown():
1096  self.DragMap(move)
1097 
1098  # dragging decoration overlay item
1099  elif (self.mouse['use'] == 'pointer' and
1100  not digitToolbar and
1101  self.dragid != None):
1102  self.DragItem(self.dragid, event)
1103 
1104  # dragging anything else - rubber band box or line
1105  else:
1106  if (self.mouse['use'] == 'pointer' and
1107  not digitToolbar):
1108  return
1109 
1110  self.mouse['end'] = event.GetPositionTuple()[:]
1111  if (event.LeftIsDown() and
1112  not (digitToolbar and
1113  digitToolbar.GetAction() in ("moveLine",) and
1114  self.digit.GetDisplay().GetSelected() > 0)):
1115  self.MouseDraw(pdc = self.pdcTmp)
1116 
1117  def OnLeftDown(self, event):
1118  """!Left mouse button pressed
1119  """
1120  Debug.msg (5, "BufferedWindow.OnLeftDown(): use=%s" % \
1121  self.mouse["use"])
1122 
1123  self.mouse['begin'] = event.GetPositionTuple()[:]
1124 
1125  if self.mouse["use"] in ["measure", "profile"]:
1126  # measure or profile
1127  if len(self.polycoords) == 0:
1128  self.mouse['end'] = self.mouse['begin']
1129  self.polycoords.append(self.Pixel2Cell(self.mouse['begin']))
1130  self.ClearLines(pdc=self.pdcTmp)
1131  else:
1132  self.mouse['begin'] = self.mouse['end']
1133 
1134  elif self.mouse['use'] == 'zoom':
1135  pass
1136 
1137  # vector digizer
1138  elif self.mouse["use"] == "pointer" and \
1139  hasattr(self, "digit"):
1140  if event.ControlDown():
1141  self.OnLeftDownUndo(event)
1142  else:
1143  self._onLeftDown(event)
1144 
1145  elif self.mouse['use'] == 'pointer':
1146  # get decoration or text id
1147  self.idlist = []
1148  self.dragid = ''
1149  self.lastpos = self.mouse['begin']
1150  idlist = self.pdc.FindObjects(self.lastpos[0], self.lastpos[1],
1151  self.hitradius)
1152  if 99 in idlist:
1153  idlist.remove(99)
1154  if idlist != []:
1155  self.dragid = idlist[0] #drag whatever is on top
1156  else:
1157  pass
1158 
1159  event.Skip()
1160 
1161  def OnLeftUp(self, event):
1162  """!Left mouse button released
1163  """
1164  Debug.msg (5, "BufferedWindow.OnLeftUp(): use=%s" % \
1165  self.mouse["use"])
1166 
1167  self.mouse['end'] = event.GetPositionTuple()[:]
1168 
1169  if self.mouse['use'] in ["zoom", "pan"]:
1170  # set region in zoom or pan
1171  begin = self.mouse['begin']
1172  end = self.mouse['end']
1173 
1174  if self.mouse['use'] == 'zoom':
1175  # set region for click (zero-width box)
1176  if begin[0] - end[0] == 0 or \
1177  begin[1] - end[1] == 0:
1178  # zoom 1/2 of the screen (TODO: settings)
1179  begin = (end[0] - self.Map.width / 4,
1180  end[1] - self.Map.height / 4)
1181  end = (end[0] + self.Map.width / 4,
1182  end[1] + self.Map.height / 4)
1183 
1184  self.Zoom(begin, end, self.zoomtype)
1185 
1186  # redraw map
1187  self.UpdateMap(render = True)
1188 
1189  # update statusbar
1190  self.parent.StatusbarUpdate()
1191 
1192  elif self.mouse["use"] == "query":
1193  # querying
1194  layers = self.GetSelectedLayer(multi = True)
1195  isRaster = False
1196  nVectors = 0
1197  for l in layers:
1198  if l.GetType() == 'raster':
1199  isRaster = True
1200  break
1201  if l.GetType() == 'vector':
1202  nVectors += 1
1203 
1204  if isRaster or nVectors > 1:
1205  self.parent.QueryMap(self.mouse['begin'][0],self.mouse['begin'][1])
1206  else:
1207  self.parent.QueryVector(self.mouse['begin'][0], self.mouse['begin'][1])
1208  # clear temp canvas
1209  self.UpdateMap(render = False, renderVector = False)
1210 
1211  elif self.mouse["use"] == "queryVector":
1212  # editable mode for vector map layers
1213  self.parent.QueryVector(self.mouse['begin'][0], self.mouse['begin'][1])
1214 
1215  # clear temp canvas
1216  self.UpdateMap(render = False, renderVector = False)
1217 
1218  elif self.mouse["use"] in ["measure", "profile"]:
1219  # measure or profile
1220  if self.mouse["use"] == "measure":
1221  self.parent.MeasureDist(self.mouse['begin'], self.mouse['end'])
1222 
1223  self.polycoords.append(self.Pixel2Cell(self.mouse['end']))
1224  self.ClearLines(pdc = self.pdcTmp)
1225  self.DrawLines(pdc = self.pdcTmp)
1226 
1227  elif self.mouse["use"] == "pointer" and \
1228  self.parent.GetLayerManager().gcpmanagement:
1229  # -> GCP manager
1230  if self.parent.toolbars['gcpdisp']:
1231  coord = self.Pixel2Cell(self.mouse['end'])
1232  if self.parent.MapWindow == self.parent.SrcMapWindow:
1233  coordtype = 'source'
1234  else:
1235  coordtype = 'target'
1236 
1237  self.parent.GetLayerManager().gcpmanagement.SetGCPData(coordtype, coord, self, confirm = True)
1238  self.UpdateMap(render = False, renderVector = False)
1239 
1240  elif self.mouse["use"] == "pointer" and \
1241  self.parent.GetLayerManager().georectifying:
1242  # -> georectifying
1243  coord = self.Pixel2Cell(self.mouse['end'])
1244  if self.parent.toolbars['georect']:
1245  coordtype = 'gcpcoord'
1246  else:
1247  coordtype = 'mapcoord'
1248 
1249  self.parent.GetLayerManager().georectifying.SetGCPData(coordtype, coord, self)
1250  self.UpdateMap(render = False, renderVector = False)
1251 
1252  elif self.mouse["use"] == "pointer" and \
1253  hasattr(self, "digit"):
1254  self._onLeftUp(event)
1255 
1256  elif (self.mouse['use'] == 'pointer' and
1257  self.dragid >= 0):
1258  # end drag of overlay decoration
1259 
1260  if self.dragid < 99 and self.dragid in self.overlays:
1261  self.overlays[self.dragid]['coords'] = self.pdc.GetIdBounds(self.dragid)
1262  elif self.dragid > 100 and self.dragid in self.textdict:
1263  self.textdict[self.dragid]['coords'] = self.pdc.GetIdBounds(self.dragid)
1264  else:
1265  pass
1266  self.dragid = None
1267  self.currtxtid = None
1268 
1269  def OnButtonDClick(self, event):
1270  """!Mouse button double click
1271  """
1272  Debug.msg (5, "BufferedWindow.OnButtonDClick(): use=%s" % \
1273  self.mouse["use"])
1274 
1275  if self.mouse["use"] == "measure":
1276  # measure
1277  self.ClearLines(pdc=self.pdcTmp)
1278  self.polycoords = []
1279  self.mouse['use'] = 'pointer'
1280  self.mouse['box'] = 'point'
1281  self.mouse['end'] = [0, 0]
1282  self.Refresh()
1283  self.SetCursor(self.parent.cursors["default"])
1284 
1285  elif self.mouse["use"] != "profile" or \
1286  (self.mouse['use'] != 'pointer' and \
1287  hasattr(self, "digit")):
1288  # select overlay decoration options dialog
1289  clickposition = event.GetPositionTuple()[:]
1290  idlist = self.pdc.FindObjects(clickposition[0], clickposition[1], self.hitradius)
1291  if idlist == []:
1292  return
1293  self.dragid = idlist[0]
1294 
1295  # self.ovlcoords[self.dragid] = self.pdc.GetIdBounds(self.dragid)
1296  if self.dragid > 100:
1297  self.currtxtid = self.dragid
1298  self.parent.OnAddText(None)
1299  elif self.dragid == 0:
1300  self.parent.OnAddBarscale(None)
1301  elif self.dragid == 1:
1302  self.parent.OnAddLegend(None)
1303 
1304  def OnRightDown(self, event):
1305  """!Right mouse button pressed
1306  """
1307  Debug.msg (5, "BufferedWindow.OnRightDown(): use=%s" % \
1308  self.mouse["use"])
1309 
1310  if hasattr(self, "digit"):
1311  self._onRightDown(event)
1312 
1313  event.Skip()
1314 
1315  def OnRightUp(self, event):
1316  """!Right mouse button released
1317  """
1318  Debug.msg (5, "BufferedWindow.OnRightUp(): use=%s" % \
1319  self.mouse["use"])
1320 
1321  if hasattr(self, "digit"):
1322  self._onRightUp(event)
1323 
1324  self.redrawAll = True
1325  self.Refresh()
1326 
1327  event.Skip()
1328 
1329  def OnMiddleDown(self, event):
1330  """!Middle mouse button pressed
1331  """
1332  if not event:
1333  return
1334 
1335  self.mouse['begin'] = event.GetPositionTuple()[:]
1336 
1337  def OnMiddleUp(self, event):
1338  """!Middle mouse button released
1339  """
1340  self.mouse['end'] = event.GetPositionTuple()[:]
1341 
1342  # set region in zoom or pan
1343  begin = self.mouse['begin']
1344  end = self.mouse['end']
1345 
1346  self.Zoom(begin, end, 0) # no zoom
1347 
1348  # redraw map
1349  self.UpdateMap(render = True)
1350 
1351  # update statusbar
1352  self.parent.StatusbarUpdate()
1353 
1354  def OnMouseEnter(self, event):
1355  """!Mouse entered window and no mouse buttons were pressed
1356  """
1357  if self.parent.GetLayerManager().gcpmanagement:
1358  if self.parent.toolbars['gcpdisp']:
1359  if not self.parent.MapWindow == self:
1360  self.parent.MapWindow = self
1361  self.parent.Map = self.Map
1362  self.parent.UpdateActive(self)
1363  # needed for wingrass
1364  self.SetFocus()
1365  else:
1366  event.Skip()
1367 
1368  def OnMouseMoving(self, event):
1369  """!Motion event and no mouse buttons were pressed
1370  """
1371  if self.mouse["use"] == "pointer" and \
1372  hasattr(self, "digit"):
1373  self._onMouseMoving(event)
1374 
1375  event.Skip()
1376 
1377  def ClearLines(self, pdc = None):
1378  """!Clears temporary drawn lines from PseudoDC
1379  """
1380  if not pdc:
1381  pdc = self.pdcTmp
1382  try:
1383  pdc.ClearId(self.lineid)
1384  pdc.RemoveId(self.lineid)
1385  except:
1386  pass
1387 
1388  try:
1389  pdc.ClearId(self.plineid)
1390  pdc.RemoveId(self.plineid)
1391  except:
1392  pass
1393 
1394  Debug.msg(4, "BufferedWindow.ClearLines(): lineid=%s, plineid=%s" %
1395  (self.lineid, self.plineid))
1396 
1397  return True
1398 
1399  def Pixel2Cell(self, (x, y)):
1400  """!Convert image coordinates to real word coordinates
1401 
1402  @param x, y image coordinates
1403 
1404  @return easting, northing
1405  @return None on error
1406  """
1407  try:
1408  x = int(x)
1409  y = int(y)
1410  except:
1411  return None
1412 
1413  if self.Map.region["ewres"] > self.Map.region["nsres"]:
1414  res = self.Map.region["ewres"]
1415  else:
1416  res = self.Map.region["nsres"]
1417 
1418  w = self.Map.region["center_easting"] - (self.Map.width / 2) * res
1419  n = self.Map.region["center_northing"] + (self.Map.height / 2) * res
1420 
1421  east = w + x * res
1422  north = n - y * res
1423 
1424  return (east, north)
1425 
1426  def Cell2Pixel(self, (east, north)):
1427  """!Convert real word coordinates to image coordinates
1428  """
1429  try:
1430  east = float(east)
1431  north = float(north)
1432  except:
1433  return None
1434 
1435  if self.Map.region["ewres"] > self.Map.region["nsres"]:
1436  res = self.Map.region["ewres"]
1437  else:
1438  res = self.Map.region["nsres"]
1439 
1440  w = self.Map.region["center_easting"] - (self.Map.width / 2) * res
1441  n = self.Map.region["center_northing"] + (self.Map.height / 2) * res
1442 
1443  x = (east - w) / res
1444  y = (n - north) / res
1445 
1446  return (x, y)
1447 
1448  def Zoom(self, begin, end, zoomtype):
1449  """!
1450  Calculates new region while (un)zoom/pan-ing
1451  """
1452  x1, y1 = begin
1453  x2, y2 = end
1454  newreg = {}
1455 
1456  # threshold - too small squares do not make sense
1457  # can only zoom to windows of > 5x5 screen pixels
1458  if abs(x2-x1) > 5 and abs(y2-y1) > 5 and zoomtype != 0:
1459  if x1 > x2:
1460  x1, x2 = x2, x1
1461  if y1 > y2:
1462  y1, y2 = y2, y1
1463 
1464  # zoom in
1465  if zoomtype > 0:
1466  newreg['w'], newreg['n'] = self.Pixel2Cell((x1, y1))
1467  newreg['e'], newreg['s'] = self.Pixel2Cell((x2, y2))
1468 
1469  # zoom out
1470  elif zoomtype < 0:
1471  newreg['w'], newreg['n'] = self.Pixel2Cell((-x1 * 2, -y1 * 2))
1472  newreg['e'], newreg['s'] = self.Pixel2Cell((self.Map.width + 2 * \
1473  (self.Map.width - x2),
1474  self.Map.height + 2 * \
1475  (self.Map.height - y2)))
1476  # pan
1477  elif zoomtype == 0:
1478  dx = x1 - x2
1479  dy = y1 - y2
1480  if dx == 0 and dy == 0:
1481  dx = x1 - self.Map.width / 2
1482  dy = y1 - self.Map.height / 2
1483  newreg['w'], newreg['n'] = self.Pixel2Cell((dx, dy))
1484  newreg['e'], newreg['s'] = self.Pixel2Cell((self.Map.width + dx,
1485  self.Map.height + dy))
1486 
1487  # if new region has been calculated, set the values
1488  if newreg != {}:
1489  # LL locations
1490  if self.parent.Map.projinfo['proj'] == 'll':
1491  if newreg['n'] > 90.0:
1492  newreg['n'] = 90.0
1493  if newreg['s'] < -90.0:
1494  newreg['s'] = -90.0
1495 
1496  ce = newreg['w'] + (newreg['e'] - newreg['w']) / 2
1497  cn = newreg['s'] + (newreg['n'] - newreg['s']) / 2
1498 
1499  # calculate new center point and display resolution
1500  self.Map.region['center_easting'] = ce
1501  self.Map.region['center_northing'] = cn
1502  self.Map.region["ewres"] = (newreg['e'] - newreg['w']) / self.Map.width
1503  self.Map.region["nsres"] = (newreg['n'] - newreg['s']) / self.Map.height
1504  self.Map.AlignExtentFromDisplay()
1505 
1506  if hasattr(self, "digit") and \
1507  hasattr(self, "moveInfo"):
1508  self._zoom(None)
1509 
1510  self.ZoomHistory(self.Map.region['n'], self.Map.region['s'],
1511  self.Map.region['e'], self.Map.region['w'])
1512 
1513  if self.redrawAll is False:
1514  self.redrawAll = True
1515 
1516  def ZoomBack(self):
1517  """!Zoom to previous extents in zoomhistory list
1518  """
1519  zoom = list()
1520 
1521  if len(self.zoomhistory) > 1:
1522  self.zoomhistory.pop()
1523  zoom = self.zoomhistory[-1]
1524 
1525  # disable tool if stack is empty
1526  if len(self.zoomhistory) < 2: # disable tool
1527  if self.parent.GetName() == 'MapWindow':
1528  toolbar = self.parent.toolbars['map']
1529  elif self.parent.GetName() == 'GRMapWindow':
1530  toolbar = self.parent.toolbars['georect']
1531  elif self.parent.GetName() == 'GCPMapWindow':
1532  toolbar = self.parent.toolbars['gcpdisp']
1533 
1534  toolbar.Enable('zoomback', enable = False)
1535 
1536  # zoom to selected region
1537  self.Map.GetRegion(n = zoom[0], s = zoom[1],
1538  e = zoom[2], w = zoom[3],
1539  update = True)
1540  # update map
1541  self.UpdateMap()
1542 
1543  # update statusbar
1544  self.parent.StatusbarUpdate()
1545 
1546  def ZoomHistory(self, n, s, e, w):
1547  """!Manages a list of last 10 zoom extents
1548 
1549  @param n,s,e,w north, south, east, west
1550 
1551  @return removed history item if exists (or None)
1552  """
1553  removed = None
1554  self.zoomhistory.append((n,s,e,w))
1555 
1556  if len(self.zoomhistory) > 10:
1557  removed = self.zoomhistory.pop(0)
1558 
1559  if removed:
1560  Debug.msg(4, "BufferedWindow.ZoomHistory(): hist=%s, removed=%s" %
1561  (self.zoomhistory, removed))
1562  else:
1563  Debug.msg(4, "BufferedWindow.ZoomHistory(): hist=%s" %
1564  (self.zoomhistory))
1565 
1566  # update toolbar
1567  if len(self.zoomhistory) > 1:
1568  enable = True
1569  else:
1570  enable = False
1571 
1572  if self.parent.GetName() == 'MapWindow':
1573  toolbar = self.parent.toolbars['map']
1574  elif self.parent.GetName() == 'GRMapWindow':
1575  toolbar = self.parent.toolbars['georect']
1576  elif self.parent.GetName() == 'GCPMapWindow':
1577  toolbar = self.parent.toolbars['gcpdisp']
1578 
1579  toolbar.Enable('zoomback', enable)
1580 
1581  return removed
1582 
1583  def ResetZoomHistory(self):
1584  """!Reset zoom history"""
1585  self.zoomhistory = list()
1586 
1587  def ZoomToMap(self, layers = None, ignoreNulls = False, render = True):
1588  """!Set display extents to match selected raster
1589  or vector map(s).
1590 
1591  @param layers list of layers to be zoom to
1592  @param ignoreNulls True to ignore null-values (valid only for rasters)
1593  @param render True to re-render display
1594  """
1595  zoomreg = {}
1596 
1597  if not layers:
1598  layers = self.GetSelectedLayer(multi = True)
1599 
1600  if not layers:
1601  return
1602 
1603  rast = []
1604  vect = []
1605  updated = False
1606  for l in layers:
1607  # only raster/vector layers are currently supported
1608  if l.type == 'raster':
1609  rast.append(l.GetName())
1610  elif l.type == 'vector':
1611  if hasattr(self, "digit") and \
1612  self.toolbar.GetLayer() == l:
1613  w, s, b, e, n, t = self.digit.GetDisplay().GetMapBoundingBox()
1614  self.Map.GetRegion(n = n, s = s, w = w, e = e,
1615  update = True)
1616  updated = True
1617  else:
1618  vect.append(l.name)
1619  elif l.type == 'rgb':
1620  for rname in l.GetName().splitlines():
1621  rast.append(rname)
1622 
1623  if not updated:
1624  self.Map.GetRegion(rast = rast,
1625  vect = vect,
1626  update = True)
1627 
1628  self.ZoomHistory(self.Map.region['n'], self.Map.region['s'],
1629  self.Map.region['e'], self.Map.region['w'])
1630 
1631  if render:
1632  self.UpdateMap()
1633 
1634  self.parent.StatusbarUpdate()
1635 
1636  def ZoomToWind(self):
1637  """!Set display geometry to match computational region
1638  settings (set with g.region)
1639  """
1640  self.Map.region = self.Map.GetRegion()
1641 
1642  self.ZoomHistory(self.Map.region['n'], self.Map.region['s'],
1643  self.Map.region['e'], self.Map.region['w'])
1644 
1645  self.UpdateMap()
1646 
1647  self.parent.StatusbarUpdate()
1648 
1649  def ZoomToDefault(self):
1650  """!Set display geometry to match default region settings
1651  """
1652  self.Map.region = self.Map.GetRegion(default = True)
1653  self.Map.AdjustRegion() # aling region extent to the display
1654 
1655  self.ZoomHistory(self.Map.region['n'], self.Map.region['s'],
1656  self.Map.region['e'], self.Map.region['w'])
1657 
1658  self.UpdateMap()
1659 
1660  self.parent.StatusbarUpdate()
1661 
1662  def DisplayToWind(self):
1663  """!Set computational region (WIND file) to match display
1664  extents
1665  """
1666  tmpreg = os.getenv("GRASS_REGION")
1667  if tmpreg:
1668  del os.environ["GRASS_REGION"]
1669 
1670  # We ONLY want to set extents here. Don't mess with resolution. Leave that
1671  # for user to set explicitly with g.region
1672  new = self.Map.AlignResolution()
1673  gcmd.RunCommand('g.region',
1674  parent = self,
1675  overwrite = True,
1676  n = new['n'],
1677  s = new['s'],
1678  e = new['e'],
1679  w = new['w'],
1680  rows = int(new['rows']),
1681  cols = int(new['cols']))
1682 
1683  if tmpreg:
1684  os.environ["GRASS_REGION"] = tmpreg
1685 
1686  def ZoomToSaved(self):
1687  """!Set display geometry to match extents in
1688  saved region file
1689  """
1690  dlg = gdialogs.SavedRegion(parent = self,
1691  title = _("Zoom to saved region extents"),
1692  loadsave='load')
1693 
1694  if dlg.ShowModal() == wx.ID_CANCEL or not dlg.wind:
1695  dlg.Destroy()
1696  return
1697 
1698  if not grass.find_file(name = dlg.wind, element = 'windows')['name']:
1699  wx.MessageBox(parent = self,
1700  message = _("Region <%s> not found. Operation canceled.") % dlg.wind,
1701  caption = _("Error"), style = wx.ICON_ERROR | wx.OK | wx.CENTRE)
1702  dlg.Destroy()
1703  return
1704 
1705  self.Map.GetRegion(regionName = dlg.wind,
1706  update = True)
1707 
1708  dlg.Destroy()
1709 
1710  self.ZoomHistory(self.Map.region['n'],
1711  self.Map.region['s'],
1712  self.Map.region['e'],
1713  self.Map.region['w'])
1714 
1715  self.UpdateMap()
1716 
1718  """!Save display extents to named region file.
1719  """
1720  dlg = gdialogs.SavedRegion(parent = self,
1721  title = _("Save display extents to region file"),
1722  loadsave='save')
1723 
1724  if dlg.ShowModal() == wx.ID_CANCEL or not dlg.wind:
1725  dlg.Destroy()
1726  return
1727 
1728  # test to see if it already exists and ask permission to overwrite
1729  if grass.find_file(name = dlg.wind, element = 'windows')['name']:
1730  overwrite = wx.MessageBox(parent = self,
1731  message = _("Region file <%s> already exists. "
1732  "Do you want to overwrite it?") % (dlg.wind),
1733  caption = _("Warning"), style = wx.YES_NO | wx.CENTRE)
1734  if (overwrite == wx.YES):
1735  self.SaveRegion(dlg.wind)
1736  else:
1737  self.SaveRegion(dlg.wind)
1738 
1739  dlg.Destroy()
1740 
1741  def SaveRegion(self, wind):
1742  """!Save region settings
1743 
1744  @param wind region name
1745  """
1746  new = self.Map.GetCurrentRegion()
1747 
1748  tmpreg = os.getenv("GRASS_REGION")
1749  if tmpreg:
1750  del os.environ["GRASS_REGION"]
1751 
1752  gcmd.RunCommand('g.region',
1753  overwrite = True,
1754  parent = self,
1755  flags = 'u',
1756  n = new['n'],
1757  s = new['s'],
1758  e = new['e'],
1759  w = new['w'],
1760  rows = int(new['rows']),
1761  cols = int(new['cols']),
1762  save = wind)
1763 
1764  if tmpreg:
1765  os.environ["GRASS_REGION"] = tmpreg
1766 
1767  def Distance(self, beginpt, endpt, screen = True):
1768  """!Calculete distance
1769 
1770  Ctypes required for LL-locations
1771 
1772  @param beginpt first point
1773  @param endpt second point
1774  @param screen True for screen coordinates otherwise EN
1775  """
1776  if screen:
1777  e1, n1 = self.Pixel2Cell(beginpt)
1778  e2, n2 = self.Pixel2Cell(endpt)
1779  else:
1780  e1, n1 = beginpt
1781  e2, n2 = endpt
1782 
1783  dEast = (e2 - e1)
1784  dNorth = (n2 - n1)
1785 
1786  if self.parent.Map.projinfo['proj'] == 'll' and haveCtypes:
1787  dist = gislib.G_distance(e1, n1, e2, n2)
1788  else:
1789  dist = math.sqrt(math.pow((dEast), 2) + math.pow((dNorth), 2))
1790 
1791  return (dist, (dEast, dNorth))