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