GRASS Programmer's Manual  6.4.3(2013)-r
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties 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, overlays = 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 = 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  self.PrepareDC(dc)
324 
325  # create a clipping rect from our position and size
326  # and update region
327  rgn = self.GetUpdateRegion().GetBox()
328  dc.SetClippingRect(rgn)
329 
330  switchDraw = False
331  if self.redrawAll is None:
332  self.redrawAll = True
333  switchDraw = True
334 
335  if self.redrawAll: # redraw pdc and pdcVector
336  # draw to the dc using the calculated clipping rect
337  self.pdc.DrawToDCClipped(dc, rgn)
338 
339  # draw vector map layer
340  if hasattr(self, "digit"):
341  # decorate with GDDC (transparency)
342  try:
343  gcdc = wx.GCDC(dc)
344  self.pdcVector.DrawToDCClipped(gcdc, rgn)
345  except NotImplementedError, e:
346  print >> sys.stderr, e
347  self.pdcVector.DrawToDCClipped(dc, rgn)
348 
349  self.bufferLast = None
350  else: # do not redraw pdc and pdcVector
351  if self.bufferLast is None:
352  # draw to the dc
353  self.pdc.DrawToDC(dc)
354 
355  if hasattr(self, "digit"):
356  # decorate with GDDC (transparency)
357  try:
358  gcdc = wx.GCDC(dc)
359  self.pdcVector.DrawToDC(gcdc)
360  except NotImplementedError, e:
361  print >> sys.stderr, e
362  self.pdcVector.DrawToDC(dc)
363 
364  # store buffered image
365  # self.bufferLast = wx.BitmapFromImage(self.buffer.ConvertToImage())
366  self.bufferLast = dc.GetAsBitmap(wx.Rect(0, 0, self.Map.width, self.Map.height))
367 
368  self.pdc.DrawBitmap(self.bufferLast, 0, 0, False)
369  self.pdc.DrawToDC(dc)
370 
371  # draw decorations (e.g. region box)
372  try:
373  gcdc = wx.GCDC(dc)
374  self.pdcDec.DrawToDC(gcdc)
375  except NotImplementedError, e:
376  print >> sys.stderr, e
377  self.pdcDec.DrawToDC(dc)
378 
379  # draw temporary object on the foreground
380  ### self.pdcTmp.DrawToDCClipped(dc, rgn)
381  self.pdcTmp.DrawToDC(dc)
382 
383  if switchDraw:
384  self.redrawAll = False
385 
386  def OnSize(self, event):
387  """!Scale map image so that it is the same size as the Window
388  """
389  Debug.msg(3, "BufferedWindow.OnSize():")
390 
391  # set size of the input image
392  self.Map.ChangeMapSize(self.GetClientSize())
393  # align extent based on center point and display resolution
394  # this causes that image is not resized when display windows is resized
395  ### self.Map.AlignExtentFromDisplay()
396 
397  # Make new off screen bitmap: this bitmap will always have the
398  # current drawing in it, so it can be used to save the image to
399  # a file, or whatever.
400  self.buffer = wx.EmptyBitmap(max(1, self.Map.width), max(1, self.Map.height))
401 
402  # get the image to be rendered
403  self.img = self.GetImage()
404 
405  # update map display
406  if self.img and self.Map.width + self.Map.height > 0: # scale image during resize
407  self.img = self.img.Scale(self.Map.width, self.Map.height)
408  if len(self.Map.GetListOfLayers()) > 0:
409  self.UpdateMap()
410 
411  # re-render image on idle
412  self.resize = True
413 
414  # reposition checkbox in statusbar
415  self.parent.StatusbarReposition()
416 
417  # update statusbar
418  self.parent.StatusbarUpdate()
419 
420  def OnIdle(self, event):
421  """!Only re-render a composite map image from GRASS during
422  idle time instead of multiple times during resizing.
423  """
424  if self.resize:
425  self.UpdateMap(render = True)
426 
427  event.Skip()
428 
429  def SaveToFile(self, FileName, FileType, width, height):
430  """!This draws the pseudo DC to a buffer that can be saved to
431  a file.
432 
433  @param FileName file name
434  @param FileType type of bitmap
435  @param width image width
436  @param height image height
437  """
438  busy = wx.BusyInfo(message = _("Please wait, exporting image..."),
439  parent = self)
440  wx.Yield()
441 
442  self.Map.ChangeMapSize((width, height))
443  ibuffer = wx.EmptyBitmap(max(1, width), max(1, height))
444  self.Map.Render(force = True, windres = True)
445  img = self.GetImage()
446  self.pdc.RemoveAll()
447  self.Draw(self.pdc, img, drawid = 99)
448 
449  # compute size ratio to move overlay accordingly
450  cSize = self.GetClientSizeTuple()
451  ratio = float(width) / cSize[0], float(height) / cSize[1]
452 
453  # redraw lagend, scalebar
454  for img in self.GetOverlay():
455  # draw any active and defined overlays
456  if self.imagedict[img]['layer'].IsActive():
457  id = self.imagedict[img]['id']
458  coords = int(ratio[0] * self.overlays[id].coords[0]),\
459  int(ratio[1] * self.overlays[id].coords[1])
460  self.Draw(self.pdc, img = img, drawid = id,
461  pdctype = self.overlays[id].pdcType, coords = coords)
462 
463  # redraw text labels
464  for id in self.textdict.keys():
465  textinfo = self.textdict[id]
466  oldCoords = textinfo['coords']
467  textinfo['coords'] = ratio[0] * textinfo['coords'][0],\
468  ratio[1] * textinfo['coords'][1]
469  self.Draw(self.pdc, img = self.textdict[id], drawid = id,
470  pdctype = 'text')
471  # set back old coordinates
472  textinfo['coords'] = oldCoords
473 
474  dc = wx.BufferedDC(None, ibuffer)
475  dc.Clear()
476  self.PrepareDC(dc)
477  self.pdc.DrawToDC(dc)
478  if self.pdcVector:
479  self.pdcVector.DrawToDC(dc)
480  ibuffer.SaveFile(FileName, FileType)
481 
482  busy.Destroy()
483 
484  self.UpdateMap(render = True)
485  self.Refresh()
486 
487  def GetOverlay(self):
488  """!Converts rendered overlay files to wx.Image
489 
490  Updates self.imagedict
491 
492  @return list of images
493  """
494  imgs = []
495  for overlay in self.Map.GetListOfLayers(l_type = "overlay", l_active = True):
496  if overlay.mapfile is not None \
497  and os.path.isfile(overlay.mapfile) and os.path.getsize(overlay.mapfile):
498  img = wx.Image(overlay.mapfile, wx.BITMAP_TYPE_ANY)
499 
500  for key in self.imagedict.keys():
501  if self.imagedict[key]['id'] == overlay.id:
502  del self.imagedict[key]
503 
504  self.imagedict[img] = { 'id' : overlay.id,
505  'layer' : overlay }
506  imgs.append(img)
507 
508  return imgs
509 
510  def GetImage(self):
511  """!Converts redered map files to wx.Image
512 
513  Updates self.imagedict (id=99)
514 
515  @return wx.Image instance (map composition)
516  """
517  imgId = 99
518  if self.mapfile and self.Map.mapfile and os.path.isfile(self.Map.mapfile) and \
519  os.path.getsize(self.Map.mapfile):
520  img = wx.Image(self.Map.mapfile, wx.BITMAP_TYPE_ANY)
521  else:
522  img = None
523 
524  for key in self.imagedict.keys():
525  if self.imagedict[key]['id'] == imgId:
526  del self.imagedict[key]
527 
528  self.imagedict[img] = { 'id': imgId }
529 
530  return img
531 
532  def UpdateMap(self, render = True, renderVector = True):
533  """!Updates the canvas anytime there is a change to the
534  underlaying images or to the geometry of the canvas.
535 
536  @param render re-render map composition
537  @param renderVector re-render vector map layer enabled for editing (used for digitizer)
538  """
539  start = time.clock()
540 
541  self.resize = False
542 
543  if self.img is None:
544  render = True
545 
546  #
547  # initialize process bar (only on 'render')
548  #
549  if render or renderVector:
550  self.parent.GetProgressBar().Show()
551  if self.parent.GetProgressBar().GetRange() > 0:
552  self.parent.GetProgressBar().SetValue(1)
553 
554  #
555  # render background image if needed
556  #
557  # update layer dictionary if there has been a change in layers
558  if self.tree and self.tree.reorder:
559  self.tree.ReorderLayers()
560 
561  # reset flag for auto-rendering
562  if self.tree:
563  self.tree.rerender = False
564 
565  try:
566  if render:
567  # update display size
568  self.Map.ChangeMapSize(self.GetClientSize())
569  if self.parent.GetProperty('resolution'):
570  # use computation region resolution for rendering
571  windres = True
572  else:
573  windres = False
574  self.mapfile = self.Map.Render(force = True, mapWindow = self.parent,
575  windres = windres)
576  else:
577  self.mapfile = self.Map.Render(force = False, mapWindow = self.parent)
578  except GException, e:
579  GError(message = e.value)
580  self.mapfile = None
581 
582  self.img = self.GetImage() # id=99
583 
584  #
585  # clear pseudoDcs
586  #
587  for pdc in (self.pdc,
588  self.pdcDec,
589  self.pdcTmp):
590  pdc.Clear()
591  pdc.RemoveAll()
592 
593  #
594  # draw background map image to PseudoDC
595  #
596  if not self.img:
597  self.Draw(self.pdc, pdctype = 'clear')
598  else:
599  try:
600  id = self.imagedict[self.img]['id']
601  except:
602  return False
603 
604  self.Draw(self.pdc, self.img, drawid = id)
605 
606  #
607  # render vector map layer
608  #
609  if renderVector and hasattr(self, "digit"):
610  self._updateMap()
611  #
612  # render overlays
613  #
614  for img in self.GetOverlay():
615  # draw any active and defined overlays
616  if self.imagedict[img]['layer'].IsActive():
617  id = self.imagedict[img]['id']
618  self.Draw(self.pdc, img = img, drawid = id,
619  pdctype = self.overlays[id].pdcType, coords = self.overlays[id].coords)
620 
621  for id in self.textdict.keys():
622  self.Draw(self.pdc, img = self.textdict[id], drawid = id,
623  pdctype = 'text', coords = [10, 10, 10, 10])
624 
625  # optionally draw computational extent box
626  self.DrawCompRegionExtent()
627 
628  #
629  # redraw pdcTmp if needed
630  #
631  if len(self.polycoords) > 0:
632  self.DrawLines(self.pdcTmp)
633 
634  if not self.parent.IsStandalone() and \
635  self.parent.GetLayerManager().gcpmanagement:
636  # -> georectifier (redraw GCPs)
637  if self.parent.GetMapToolbar():
638  if self == self.parent.TgtMapWindow:
639  coordtype = 'target'
640  else:
641  coordtype = 'source'
642 
643  self.parent.DrawGCP(coordtype)
644 
645  #
646  # clear measurement
647  #
648  if self.mouse["use"] == "measure":
649  self.ClearLines(pdc = self.pdcTmp)
650  self.polycoords = []
651  self.mouse['use'] = 'pointer'
652  self.mouse['box'] = 'point'
653  self.mouse['end'] = [0, 0]
654  self.SetCursor(self.parent.cursors["default"])
655 
656  stop = time.clock()
657 
658  #
659  # hide process bar
660  #
661  self.parent.GetProgressBar().Hide()
662 
663  #
664  # update statusbar
665  #
666  ### self.Map.SetRegion()
667  self.parent.StatusbarUpdate()
668 
669  Debug.msg (1, "BufferedWindow.UpdateMap(): render=%s, renderVector=%s -> time=%g" % \
670  (render, renderVector, (stop-start)))
671 
672  return True
673 
675  """!Draw computational region extent in the display
676 
677  Display region is drawn as a blue box inside the computational region,
678  computational region inside a display region as a red box).
679  """
680  if hasattr(self, "regionCoords"):
681  compReg = self.Map.GetRegion()
682  dispReg = self.Map.GetCurrentRegion()
683  reg = None
684  if self.IsInRegion(dispReg, compReg):
685  self.polypen = wx.Pen(colour = wx.Colour(0, 0, 255, 128), width = 3, style = wx.SOLID)
686  reg = dispReg
687  else:
688  self.polypen = wx.Pen(colour = wx.Colour(255, 0, 0, 128),
689  width = 3, style = wx.SOLID)
690  reg = compReg
691 
692  self.regionCoords = []
693  self.regionCoords.append((reg['w'], reg['n']))
694  self.regionCoords.append((reg['e'], reg['n']))
695  self.regionCoords.append((reg['e'], reg['s']))
696  self.regionCoords.append((reg['w'], reg['s']))
697  self.regionCoords.append((reg['w'], reg['n']))
698  # draw region extent
699  self.DrawLines(pdc = self.pdcDec, polycoords = self.regionCoords)
700 
701  def IsInRegion(self, region, refRegion):
702  """!
703  Test if 'region' is inside of 'refRegion'
704 
705  @param region input region
706  @param refRegion reference region (e.g. computational region)
707 
708  @return True if region is inside of refRegion
709  @return False
710  """
711  if region['s'] >= refRegion['s'] and \
712  region['n'] <= refRegion['n'] and \
713  region['w'] >= refRegion['w'] and \
714  region['e'] <= refRegion['e']:
715  return True
716 
717  return False
718 
719  def EraseMap(self):
720  """!Erase map canvas
721  """
722  self.Draw(self.pdc, pdctype = 'clear')
723 
724  if hasattr(self, "digit"):
725  self.Draw(self.pdcVector, pdctype = 'clear')
726 
727  self.Draw(self.pdcDec, pdctype = 'clear')
728  self.Draw(self.pdcTmp, pdctype = 'clear')
729 
730  def DragMap(self, moveto):
731  """!Drag the entire map image for panning.
732 
733  @param moveto dx,dy
734  """
735  dc = wx.BufferedDC(wx.ClientDC(self))
736  dc.SetBackground(wx.Brush("White"))
737  dc.Clear()
738 
739  self.dragimg = wx.DragImage(self.buffer)
740  self.dragimg.BeginDrag((0, 0), self)
741  self.dragimg.GetImageRect(moveto)
742  self.dragimg.Move(moveto)
743 
744  self.dragimg.DoDrawImage(dc, moveto)
745  self.dragimg.EndDrag()
746 
747  def DragItem(self, id, event):
748  """!Drag an overlay decoration item
749  """
750  if id == 99 or id == '' or id == None: return
751  Debug.msg (5, "BufferedWindow.DragItem(): id=%d" % id)
752  x, y = self.lastpos
753  dx = event.GetX() - x
754  dy = event.GetY() - y
755  self.pdc.SetBackground(wx.Brush(self.GetBackgroundColour()))
756  r = self.pdc.GetIdBounds(id)
757  if type(r) is list:
758  r = wx.Rect(r[0], r[1], r[2], r[3])
759  if id > 100: # text dragging
760  rtop = (r[0],r[1]-r[3],r[2],r[3])
761  r = r.Union(rtop)
762  rleft = (r[0]-r[2],r[1],r[2],r[3])
763  r = r.Union(rleft)
764  self.pdc.TranslateId(id, dx, dy)
765 
766  r2 = self.pdc.GetIdBounds(id)
767  if type(r2) is list:
768  r2 = wx.Rect(r[0], r[1], r[2], r[3])
769  if id > 100: # text
770  self.textdict[id]['bbox'] = r2
771  self.textdict[id]['coords'][0] += dx
772  self.textdict[id]['coords'][1] += dy
773  r = r.Union(r2)
774  r.Inflate(4,4)
775  self.RefreshRect(r, False)
776  self.lastpos = (event.GetX(), event.GetY())
777 
778  def MouseDraw(self, pdc = None, begin = None, end = None):
779  """!Mouse box or line from 'begin' to 'end'
780 
781  If not given from self.mouse['begin'] to self.mouse['end'].
782  """
783  if not pdc:
784  return
785 
786  if begin is None:
787  begin = self.mouse['begin']
788  if end is None:
789  end = self.mouse['end']
790 
791  Debug.msg (5, "BufferedWindow.MouseDraw(): use=%s, box=%s, begin=%f,%f, end=%f,%f" % \
792  (self.mouse['use'], self.mouse['box'],
793  begin[0], begin[1], end[0], end[1]))
794 
795  if self.mouse['box'] == "box":
796  boxid = wx.ID_NEW
797  mousecoords = [begin[0], begin[1],
798  end[0], end[1]]
799  r = pdc.GetIdBounds(boxid)
800  if type(r) is list:
801  r = wx.Rect(r[0], r[1], r[2], r[3])
802  r.Inflate(4, 4)
803  try:
804  pdc.ClearId(boxid)
805  except:
806  pass
807  self.RefreshRect(r, False)
808  pdc.SetId(boxid)
809  self.Draw(pdc, drawid = boxid, pdctype = 'box', coords = mousecoords)
810 
811  elif self.mouse['box'] == "line":
812  self.lineid = wx.ID_NEW
813  mousecoords = [begin[0], begin[1], \
814  end[0], end[1]]
815  x1 = min(begin[0],end[0])
816  x2 = max(begin[0],end[0])
817  y1 = min(begin[1],end[1])
818  y2 = max(begin[1],end[1])
819  r = wx.Rect(x1,y1,x2-x1,y2-y1)
820  r.Inflate(4,4)
821  try:
822  pdc.ClearId(self.lineid)
823  except:
824  pass
825  self.RefreshRect(r, False)
826  pdc.SetId(self.lineid)
827  self.Draw(pdc, drawid = self.lineid, pdctype = 'line', coords = mousecoords)
828 
829  def DrawLines(self, pdc = None, polycoords = None):
830  """!Draw polyline in PseudoDC
831 
832  Set self.pline to wx.NEW_ID + 1
833 
834  polycoords - list of polyline vertices, geographical coordinates
835  (if not given, self.polycoords is used)
836  """
837  if not pdc:
838  pdc = self.pdcTmp
839 
840  if not polycoords:
841  polycoords = self.polycoords
842 
843  if len(polycoords) > 0:
844  self.plineid = wx.ID_NEW + 1
845  # convert from EN to XY
846  coords = []
847  for p in polycoords:
848  coords.append(self.Cell2Pixel(p))
849 
850  self.Draw(pdc, drawid = self.plineid, pdctype = 'polyline', coords = coords)
851 
852  Debug.msg (4, "BufferedWindow.DrawLines(): coords=%s, id=%s" % \
853  (coords, self.plineid))
854 
855  return self.plineid
856 
857  return -1
858 
859  def DrawCross(self, pdc, coords, size, rotation = 0,
860  text = None, textAlign = 'lr', textOffset = (5, 5)):
861  """!Draw cross in PseudoDC
862 
863  @todo implement rotation
864 
865  @param pdc PseudoDC
866  @param coord center coordinates
867  @param rotation rotate symbol
868  @param text draw also text (text, font, color, rotation)
869  @param textAlign alignment (default 'lower-right')
870  @textOffset offset for text (from center point)
871  """
872  Debug.msg(4, "BufferedWindow.DrawCross(): pdc=%s, coords=%s, size=%d" % \
873  (pdc, coords, size))
874  coordsCross = ((coords[0] - size, coords[1], coords[0] + size, coords[1]),
875  (coords[0], coords[1] - size, coords[0], coords[1] + size))
876 
877  self.lineid = wx.NewId()
878  for lineCoords in coordsCross:
879  self.Draw(pdc, drawid = self.lineid, pdctype = 'line', coords = lineCoords)
880 
881  if not text:
882  return self.lineid
883 
884  if textAlign == 'ul':
885  coord = [coords[0] - textOffset[0], coords[1] - textOffset[1], 0, 0]
886  elif textAlign == 'ur':
887  coord = [coords[0] + textOffset[0], coords[1] - textOffset[1], 0, 0]
888  elif textAlign == 'lr':
889  coord = [coords[0] + textOffset[0], coords[1] + textOffset[1], 0, 0]
890  else:
891  coord = [coords[0] - textOffset[0], coords[1] + textOffset[1], 0, 0]
892 
893  self.Draw(pdc, img = text,
894  pdctype = 'text', coords = coord)
895 
896  return self.lineid
897 
898  def _computeZoomToPointAndRecenter(self, position, zoomtype):
899  """!Computes zoom parameters for recenter mode.
900 
901  Computes begin and end parameters for Zoom() method.
902  Used for zooming by single click (not box)
903  and mouse wheel zooming (zoom and recenter mode).
904  """
905  if zoomtype > 0:
906  begin = (position[0] - self.Map.width / 4,
907  position[1] - self.Map.height / 4)
908  end = (position[0] + self.Map.width / 4,
909  position[1] + self.Map.height / 4)
910  else:
911  begin = ((self.Map.width - position[0]) / 2,
912  (self.Map.height - position[1]) / 2)
913  end = (begin[0] + self.Map.width / 2,
914  begin[1] + self.Map.height / 2)
915  return begin, end
916 
917  def MouseActions(self, event):
918  """!Mouse motion and button click notifier
919  """
920  if not self.processMouse:
921  return
922 
923  # zoom with mouse wheel
924  if event.GetWheelRotation() != 0:
925  self.OnMouseWheel(event)
926 
927  # left mouse button pressed
928  elif event.LeftDown():
929  self.OnLeftDown(event)
930 
931  # left mouse button released
932  elif event.LeftUp():
933  self.OnLeftUp(event)
934 
935  # dragging
936  elif event.Dragging():
937  self.OnDragging(event)
938 
939  # double click
940  elif event.ButtonDClick():
941  self.OnButtonDClick(event)
942 
943  # middle mouse button pressed
944  elif event.MiddleDown():
945  self.OnMiddleDown(event)
946 
947  # middle mouse button relesed
948  elif event.MiddleUp():
949  self.OnMiddleUp(event)
950 
951  # right mouse button pressed
952  elif event.RightDown():
953  self.OnRightDown(event)
954 
955  # right mouse button released
956  elif event.RightUp():
957  self.OnRightUp(event)
958 
959  elif event.Entering():
960  self.OnMouseEnter(event)
961 
962  elif event.Moving():
963  self.OnMouseMoving(event)
964 
965  def OnMouseWheel(self, event):
966  """!Mouse wheel moved
967  """
968  zoomBehaviour = UserSettings.Get(group = 'display',
969  key = 'mouseWheelZoom',
970  subkey = 'selection')
971  if zoomBehaviour == 2:
972  event.Skip()
973  return
974 
975  self.processMouse = False
976  current = event.GetPositionTuple()[:]
977  wheel = event.GetWheelRotation()
978  Debug.msg (5, "BufferedWindow.MouseAction(): wheel=%d" % wheel)
979 
980  if wheel > 0:
981  zoomtype = 1
982  else:
983  zoomtype = -1
984  if UserSettings.Get(group = 'display',
985  key = 'scrollDirection',
986  subkey = 'selection'):
987  zoomtype *= -1
988  # zoom 1/2 of the screen (TODO: settings)
989  if zoomBehaviour == 0: # zoom and recenter
990  begin, end = self._computeZoomToPointAndRecenter(position = current, zoomtype = zoomtype)
991 
992  elif zoomBehaviour == 1: # zoom to current cursor position
993  begin = (current[0]/2, current[1]/2)
994  end = ((self.Map.width - current[0])/2 + current[0],
995  (self.Map.height - current[1])/2 + current[1])
996 
997  # zoom
998  self.Zoom(begin, end, zoomtype)
999 
1000  # redraw map
1001  self.UpdateMap()
1002 
1003  # update statusbar
1004  self.parent.StatusbarUpdate()
1005 
1006  self.Refresh()
1007  self.processMouse = True
1008 
1009  def OnDragging(self, event):
1010  """!Mouse dragging
1011  """
1012  Debug.msg (5, "BufferedWindow.MouseAction(): Dragging")
1013  current = event.GetPositionTuple()[:]
1014  previous = self.mouse['begin']
1015  move = (current[0] - previous[0],
1016  current[1] - previous[1])
1017 
1018  if hasattr(self, "digit"):
1019  digitToolbar = self.toolbar
1020  else:
1021  digitToolbar = None
1022 
1023  # dragging or drawing box with left button
1024  if self.mouse['use'] == 'pan' or \
1025  event.MiddleIsDown():
1026  self.DragMap(move)
1027 
1028  # dragging decoration overlay item
1029  elif (self.mouse['use'] == 'pointer' and
1030  not digitToolbar and
1031  self.dragid != None):
1032  self.DragItem(self.dragid, event)
1033 
1034  # dragging anything else - rubber band box or line
1035  else:
1036  if (self.mouse['use'] == 'pointer' and
1037  not digitToolbar):
1038  return
1039 
1040  self.mouse['end'] = event.GetPositionTuple()[:]
1041  if (event.LeftIsDown() and
1042  not (digitToolbar and
1043  digitToolbar.GetAction() in ("moveLine",) and
1044  self.digit.GetDisplay().GetSelected() > 0)):
1045  self.MouseDraw(pdc = self.pdcTmp)
1046 
1047  def OnLeftDown(self, event):
1048  """!Left mouse button pressed
1049  """
1050  Debug.msg (5, "BufferedWindow.OnLeftDown(): use=%s" % \
1051  self.mouse["use"])
1052 
1053  self.mouse['begin'] = event.GetPositionTuple()[:]
1054 
1055  if self.mouse["use"] in ["measure", "profile"]:
1056  # measure or profile
1057  if len(self.polycoords) == 0:
1058  self.mouse['end'] = self.mouse['begin']
1059  self.polycoords.append(self.Pixel2Cell(self.mouse['begin']))
1060  self.ClearLines(pdc=self.pdcTmp)
1061  else:
1062  self.mouse['begin'] = self.mouse['end']
1063 
1064  elif self.mouse['use'] in ('zoom', 'legend'):
1065  pass
1066 
1067  # vector digizer
1068  elif self.mouse["use"] == "pointer" and \
1069  hasattr(self, "digit"):
1070  if event.ControlDown():
1071  self.OnLeftDownUndo(event)
1072  else:
1073  self._onLeftDown(event)
1074 
1075  elif self.mouse['use'] == 'pointer':
1076  # get decoration or text id
1077  self.idlist = []
1078  self.dragid = ''
1079  self.lastpos = self.mouse['begin']
1080  idlist = self.pdc.FindObjects(self.lastpos[0], self.lastpos[1],
1081  self.hitradius)
1082  if 99 in idlist:
1083  idlist.remove(99)
1084  if idlist != []:
1085  self.dragid = idlist[0] #drag whatever is on top
1086  else:
1087  pass
1088 
1089  event.Skip()
1090 
1091  def OnLeftUp(self, event):
1092  """!Left mouse button released
1093  """
1094  Debug.msg (5, "BufferedWindow.OnLeftUp(): use=%s" % \
1095  self.mouse["use"])
1096 
1097  self.mouse['end'] = event.GetPositionTuple()[:]
1098 
1099  if self.mouse['use'] in ["zoom", "pan"]:
1100  # set region in zoom or pan
1101  begin = self.mouse['begin']
1102  end = self.mouse['end']
1103 
1104  if self.mouse['use'] == 'zoom':
1105  # set region for click (zero-width box)
1106  if begin[0] - end[0] == 0 or \
1107  begin[1] - end[1] == 0:
1108  begin, end = self._computeZoomToPointAndRecenter(position = end, zoomtype = self.zoomtype)
1109  self.Zoom(begin, end, self.zoomtype)
1110 
1111  # redraw map
1112  self.UpdateMap(render = True)
1113 
1114  # update statusbar
1115  self.parent.StatusbarUpdate()
1116 
1117  elif self.mouse["use"] == "query":
1118  # querying
1119  if self.parent.IsStandalone():
1120  GMessage(parent = self.parent,
1121  message = _("Querying is not implemented in standalone mode of Map Display"))
1122  return
1123 
1124  layers = self.GetSelectedLayer(type = 'item', multi = True)
1125 
1126  self.parent.Query(self.mouse['begin'][0],self.mouse['begin'][1], layers)
1127 
1128  elif self.mouse["use"] in ["measure", "profile"]:
1129  # measure or profile
1130  if self.mouse["use"] == "measure":
1131  self.parent.MeasureDist(self.mouse['begin'], self.mouse['end'])
1132 
1133  self.polycoords.append(self.Pixel2Cell(self.mouse['end']))
1134  self.ClearLines(pdc = self.pdcTmp)
1135  self.DrawLines(pdc = self.pdcTmp)
1136 
1137  elif self.mouse["use"] == "pointer" and \
1138  self.parent.GetLayerManager().gcpmanagement:
1139  # -> GCP manager
1140  if self.parent.GetToolbar('gcpdisp'):
1141  coord = self.Pixel2Cell(self.mouse['end'])
1142  if self.parent.MapWindow == self.parent.SrcMapWindow:
1143  coordtype = 'source'
1144  else:
1145  coordtype = 'target'
1146 
1147  self.parent.GetLayerManager().gcpmanagement.SetGCPData(coordtype, coord, self, confirm = True)
1148  self.UpdateMap(render = False, renderVector = False)
1149 
1150  elif self.mouse["use"] == "pointer" and \
1151  hasattr(self, "digit"):
1152  self._onLeftUp(event)
1153 
1154  elif (self.mouse['use'] == 'pointer' and
1155  self.dragid >= 0):
1156  # end drag of overlay decoration
1157 
1158  if self.dragid < 99 and self.dragid in self.overlays:
1159  self.overlays[self.dragid].coords = self.pdc.GetIdBounds(self.dragid)
1160  elif self.dragid > 100 and self.dragid in self.textdict:
1161  self.textdict[self.dragid]['bbox'] = self.pdc.GetIdBounds(self.dragid)
1162  else:
1163  pass
1164  self.dragid = None
1165  self.currtxtid = None
1166 
1167  elif self.mouse['use'] == 'legend':
1168  self.parent.dialogs['legend'].resizeBtn.SetValue(False)
1169  screenSize = self.GetClientSizeTuple()
1170  self.overlays[1].ResizeLegend(self.mouse["begin"], self.mouse["end"], screenSize)
1171 
1172  self.parent.MapWindow.SetCursor(self.parent.cursors["default"])
1173  self.parent.MapWindow.mouse['use'] = 'pointer'
1174 
1175  self.UpdateMap()
1176 
1177  def OnButtonDClick(self, event):
1178  """!Mouse button double click
1179  """
1180  Debug.msg (5, "BufferedWindow.OnButtonDClick(): use=%s" % \
1181  self.mouse["use"])
1182 
1183  if self.mouse["use"] == "measure":
1184  # measure
1185  self.ClearLines(pdc=self.pdcTmp)
1186  self.polycoords = []
1187  self.mouse['use'] = 'pointer'
1188  self.mouse['box'] = 'point'
1189  self.mouse['end'] = [0, 0]
1190  self.Refresh()
1191  self.SetCursor(self.parent.cursors["default"])
1192 
1193  elif self.mouse["use"] != "profile" or \
1194  (self.mouse['use'] != 'pointer' and \
1195  hasattr(self, "digit")):
1196  # select overlay decoration options dialog
1197  clickposition = event.GetPositionTuple()[:]
1198  idlist = self.pdc.FindObjects(clickposition[0], clickposition[1], self.hitradius)
1199  if idlist == []:
1200  return
1201  self.dragid = idlist[0]
1202 
1203  # self.ovlcoords[self.dragid] = self.pdc.GetIdBounds(self.dragid)
1204  if self.dragid > 100:
1205  self.currtxtid = self.dragid
1206  self.parent.OnAddText(None)
1207  elif self.dragid == 0:
1208  self.parent.AddBarscale()
1209  elif self.dragid == 1:
1210  self.parent.AddLegend()
1211 
1212  def OnRightDown(self, event):
1213  """!Right mouse button pressed
1214  """
1215  Debug.msg (5, "BufferedWindow.OnRightDown(): use=%s" % \
1216  self.mouse["use"])
1217 
1218  if hasattr(self, "digit"):
1219  self._onRightDown(event)
1220 
1221  event.Skip()
1222 
1223  def OnRightUp(self, event):
1224  """!Right mouse button released
1225  """
1226  Debug.msg (5, "BufferedWindow.OnRightUp(): use=%s" % \
1227  self.mouse["use"])
1228 
1229  if hasattr(self, "digit"):
1230  self._onRightUp(event)
1231 
1232  self.redrawAll = True
1233  self.Refresh()
1234 
1235  event.Skip()
1236 
1237  def OnMiddleDown(self, event):
1238  """!Middle mouse button pressed
1239  """
1240  if not event:
1241  return
1242 
1243  self.mouse['begin'] = event.GetPositionTuple()[:]
1244 
1245  def OnMiddleUp(self, event):
1246  """!Middle mouse button released
1247  """
1248  self.mouse['end'] = event.GetPositionTuple()[:]
1249 
1250  # set region in zoom or pan
1251  begin = self.mouse['begin']
1252  end = self.mouse['end']
1253 
1254  self.Zoom(begin, end, 0) # no zoom
1255 
1256  # redraw map
1257  self.UpdateMap(render = True)
1258 
1259  # update statusbar
1260  self.parent.StatusbarUpdate()
1261 
1262  def OnMouseEnter(self, event):
1263  """!Mouse entered window and no mouse buttons were pressed
1264  """
1265  if self.parent.GetLayerManager().gcpmanagement:
1266  if self.parent.GetToolbar('gcpdisp'):
1267  if not self.parent.MapWindow == self:
1268  self.parent.MapWindow = self
1269  self.parent.Map = self.Map
1270  self.parent.UpdateActive(self)
1271  # needed for wingrass
1272  self.SetFocus()
1273  else:
1274  event.Skip()
1275 
1276  def OnMouseMoving(self, event):
1277  """!Motion event and no mouse buttons were pressed
1278  """
1279  if self.mouse["use"] == "pointer" and \
1280  hasattr(self, "digit"):
1281  self._onMouseMoving(event)
1282 
1283  event.Skip()
1284 
1285  def ClearLines(self, pdc = None):
1286  """!Clears temporary drawn lines from PseudoDC
1287  """
1288  if not pdc:
1289  pdc = self.pdcTmp
1290  try:
1291  pdc.ClearId(self.lineid)
1292  pdc.RemoveId(self.lineid)
1293  except:
1294  pass
1295 
1296  try:
1297  pdc.ClearId(self.plineid)
1298  pdc.RemoveId(self.plineid)
1299  except:
1300  pass
1301 
1302  Debug.msg(4, "BufferedWindow.ClearLines(): lineid=%s, plineid=%s" %
1303  (self.lineid, self.plineid))
1304 
1305  return True
1306 
1307  def Pixel2Cell(self, (x, y)):
1308  """!Convert image coordinates to real word coordinates
1309 
1310  @param x, y image coordinates
1311 
1312  @return easting, northing
1313  @return None on error
1314  """
1315  try:
1316  x = int(x)
1317  y = int(y)
1318  except:
1319  return None
1320 
1321  if self.Map.region["ewres"] > self.Map.region["nsres"]:
1322  res = self.Map.region["ewres"]
1323  else:
1324  res = self.Map.region["nsres"]
1325 
1326  w = self.Map.region["center_easting"] - (self.Map.width / 2) * res
1327  n = self.Map.region["center_northing"] + (self.Map.height / 2) * res
1328 
1329  east = w + x * res
1330  north = n - y * res
1331 
1332  return (east, north)
1333 
1334  def Cell2Pixel(self, (east, north)):
1335  """!Convert real word coordinates to image coordinates
1336  """
1337  try:
1338  east = float(east)
1339  north = float(north)
1340  except:
1341  return None
1342 
1343  if self.Map.region["ewres"] > self.Map.region["nsres"]:
1344  res = self.Map.region["ewres"]
1345  else:
1346  res = self.Map.region["nsres"]
1347 
1348  w = self.Map.region["center_easting"] - (self.Map.width / 2) * res
1349  n = self.Map.region["center_northing"] + (self.Map.height / 2) * res
1350 
1351  x = (east - w) / res
1352  y = (n - north) / res
1353 
1354  return (x, y)
1355 
1356  def Zoom(self, begin, end, zoomtype):
1357  """!
1358  Calculates new region while (un)zoom/pan-ing
1359  """
1360  x1, y1 = begin
1361  x2, y2 = end
1362  newreg = {}
1363 
1364  # threshold - too small squares do not make sense
1365  # can only zoom to windows of > 5x5 screen pixels
1366  if abs(x2-x1) > 5 and abs(y2-y1) > 5 and zoomtype != 0:
1367  if x1 > x2:
1368  x1, x2 = x2, x1
1369  if y1 > y2:
1370  y1, y2 = y2, y1
1371 
1372  # zoom in
1373  if zoomtype > 0:
1374  newreg['w'], newreg['n'] = self.Pixel2Cell((x1, y1))
1375  newreg['e'], newreg['s'] = self.Pixel2Cell((x2, y2))
1376 
1377  # zoom out
1378  elif zoomtype < 0:
1379  newreg['w'], newreg['n'] = self.Pixel2Cell((-x1 * 2, -y1 * 2))
1380  newreg['e'], newreg['s'] = self.Pixel2Cell((self.Map.width + 2 * \
1381  (self.Map.width - x2),
1382  self.Map.height + 2 * \
1383  (self.Map.height - y2)))
1384  # pan
1385  elif zoomtype == 0:
1386  dx = x1 - x2
1387  dy = y1 - y2
1388  if dx == 0 and dy == 0:
1389  dx = x1 - self.Map.width / 2
1390  dy = y1 - self.Map.height / 2
1391  newreg['w'], newreg['n'] = self.Pixel2Cell((dx, dy))
1392  newreg['e'], newreg['s'] = self.Pixel2Cell((self.Map.width + dx,
1393  self.Map.height + dy))
1394 
1395  # if new region has been calculated, set the values
1396  if newreg != {}:
1397  # LL locations
1398  if self.Map.projinfo['proj'] == 'll':
1399  self.Map.region['n'] = min(self.Map.region['n'], 90.0)
1400  self.Map.region['s'] = max(self.Map.region['s'], -90.0)
1401 
1402  ce = newreg['w'] + (newreg['e'] - newreg['w']) / 2
1403  cn = newreg['s'] + (newreg['n'] - newreg['s']) / 2
1404 
1405  # calculate new center point and display resolution
1406  self.Map.region['center_easting'] = ce
1407  self.Map.region['center_northing'] = cn
1408  self.Map.region['ewres'] = (newreg['e'] - newreg['w']) / self.Map.width
1409  self.Map.region['nsres'] = (newreg['n'] - newreg['s']) / self.Map.height
1410  if not self.parent.HasProperty('alignExtent') or \
1411  self.parent.GetProperty('alignExtent'):
1412  self.Map.AlignExtentFromDisplay()
1413  else:
1414  for k in ('n', 's', 'e', 'w'):
1415  self.Map.region[k] = newreg[k]
1416 
1417  if hasattr(self, "digit") and \
1418  hasattr(self, "moveInfo"):
1419  self._zoom(None)
1420 
1421  self.ZoomHistory(self.Map.region['n'], self.Map.region['s'],
1422  self.Map.region['e'], self.Map.region['w'])
1423 
1424  if self.redrawAll is False:
1425  self.redrawAll = True
1426 
1427  def ZoomBack(self):
1428  """!Zoom to previous extents in zoomhistory list
1429  """
1430  zoom = list()
1431 
1432  if len(self.zoomhistory) > 1:
1433  self.zoomhistory.pop()
1434  zoom = self.zoomhistory[-1]
1435 
1436  # disable tool if stack is empty
1437  if len(self.zoomhistory) < 2: # disable tool
1438  toolbar = self.parent.GetMapToolbar()
1439  toolbar.Enable('zoomBack', enable = False)
1440 
1441  # zoom to selected region
1442  self.Map.GetRegion(n = zoom[0], s = zoom[1],
1443  e = zoom[2], w = zoom[3],
1444  update = True)
1445  # update map
1446  self.UpdateMap()
1447 
1448  # update statusbar
1449  self.parent.StatusbarUpdate()
1450 
1451  def ZoomHistory(self, n, s, e, w):
1452  """!Manages a list of last 10 zoom extents
1453 
1454  @param n,s,e,w north, south, east, west
1455 
1456  @return removed history item if exists (or None)
1457  """
1458  removed = None
1459  self.zoomhistory.append((n,s,e,w))
1460 
1461  if len(self.zoomhistory) > 10:
1462  removed = self.zoomhistory.pop(0)
1463 
1464  if removed:
1465  Debug.msg(4, "BufferedWindow.ZoomHistory(): hist=%s, removed=%s" %
1466  (self.zoomhistory, removed))
1467  else:
1468  Debug.msg(4, "BufferedWindow.ZoomHistory(): hist=%s" %
1469  (self.zoomhistory))
1470 
1471  # update toolbar
1472  if len(self.zoomhistory) > 1:
1473  enable = True
1474  else:
1475  enable = False
1476 
1477  toolbar = self.parent.GetMapToolbar()
1478 
1479  toolbar.Enable('zoomBack', enable)
1480 
1481  return removed
1482 
1483  def ResetZoomHistory(self):
1484  """!Reset zoom history"""
1485  self.zoomhistory = list()
1486 
1487  def ZoomToMap(self, layers = None, ignoreNulls = False, render = True):
1488  """!Set display extents to match selected raster
1489  or vector map(s).
1490 
1491  @param layers list of layers to be zoom to
1492  @param ignoreNulls True to ignore null-values (valid only for rasters)
1493  @param render True to re-render display
1494  """
1495  zoomreg = {}
1496 
1497  if not layers:
1498  layers = self.GetSelectedLayer(multi = True)
1499 
1500  if not layers:
1501  return
1502 
1503  rast = []
1504  vect = []
1505  updated = False
1506  for l in layers:
1507  # only raster/vector layers are currently supported
1508  if l.type == 'raster':
1509  rast.append(l.GetName())
1510  elif l.type == 'vector':
1511  if hasattr(self, "digit") and \
1512  self.toolbar.GetLayer() == l:
1513  w, s, b, e, n, t = self.digit.GetDisplay().GetMapBoundingBox()
1514  self.Map.GetRegion(n = n, s = s, w = w, e = e,
1515  update = True)
1516  updated = True
1517  else:
1518  vect.append(l.name)
1519  elif l.type == 'rgb':
1520  for rname in l.GetName().splitlines():
1521  rast.append(rname)
1522 
1523  if not updated:
1524  self.Map.GetRegion(rast = rast,
1525  vect = vect,
1526  update = True)
1527 
1528  self.ZoomHistory(self.Map.region['n'], self.Map.region['s'],
1529  self.Map.region['e'], self.Map.region['w'])
1530 
1531  if render:
1532  self.UpdateMap()
1533 
1534  self.parent.StatusbarUpdate()
1535 
1536  def ZoomToWind(self):
1537  """!Set display geometry to match computational region
1538  settings (set with g.region)
1539  """
1540  self.Map.region = self.Map.GetRegion()
1541 
1542  self.ZoomHistory(self.Map.region['n'], self.Map.region['s'],
1543  self.Map.region['e'], self.Map.region['w'])
1544 
1545  self.UpdateMap()
1546 
1547  self.parent.StatusbarUpdate()
1548 
1549  def ZoomToDefault(self):
1550  """!Set display geometry to match default region settings
1551  """
1552  self.Map.region = self.Map.GetRegion(default = True)
1553  self.Map.AdjustRegion() # aling region extent to the display
1554 
1555  self.ZoomHistory(self.Map.region['n'], self.Map.region['s'],
1556  self.Map.region['e'], self.Map.region['w'])
1557 
1558  self.UpdateMap()
1559 
1560  self.parent.StatusbarUpdate()
1561 
1562 
1563  def GoTo(self, e, n):
1564  region = self.Map.GetCurrentRegion()
1565 
1566  region['center_easting'], region['center_northing'] = e, n
1567 
1568  dn = (region['nsres'] * region['rows']) / 2.
1569  region['n'] = region['center_northing'] + dn
1570  region['s'] = region['center_northing'] - dn
1571  de = (region['ewres'] * region['cols']) / 2.
1572  region['e'] = region['center_easting'] + de
1573  region['w'] = region['center_easting'] - de
1574 
1575  self.Map.AdjustRegion()
1576 
1577  # add to zoom history
1578  self.ZoomHistory(region['n'], region['s'],
1579  region['e'], region['w'])
1580  self.UpdateMap()
1581 
1582  def DisplayToWind(self):
1583  """!Set computational region (WIND file) to match display
1584  extents
1585  """
1586  tmpreg = os.getenv("GRASS_REGION")
1587  if tmpreg:
1588  del os.environ["GRASS_REGION"]
1589 
1590  # We ONLY want to set extents here. Don't mess with resolution. Leave that
1591  # for user to set explicitly with g.region
1592  new = self.Map.AlignResolution()
1593  RunCommand('g.region',
1594  parent = self,
1595  overwrite = True,
1596  n = new['n'],
1597  s = new['s'],
1598  e = new['e'],
1599  w = new['w'],
1600  rows = int(new['rows']),
1601  cols = int(new['cols']))
1602 
1603  if tmpreg:
1604  os.environ["GRASS_REGION"] = tmpreg
1605 
1606  def ZoomToSaved(self):
1607  """!Set display geometry to match extents in
1608  saved region file
1609  """
1610  dlg = SavedRegion(parent = self,
1611  title = _("Zoom to saved region extents"),
1612  loadsave='load')
1613 
1614  if dlg.ShowModal() == wx.ID_CANCEL or not dlg.wind:
1615  dlg.Destroy()
1616  return
1617 
1618  if not grass.find_file(name = dlg.wind, element = 'windows')['name']:
1619  wx.MessageBox(parent = self,
1620  message = _("Region <%s> not found. Operation canceled.") % dlg.wind,
1621  caption = _("Error"), style = wx.ICON_ERROR | wx.OK | wx.CENTRE)
1622  dlg.Destroy()
1623  return
1624 
1625  self.Map.GetRegion(regionName = dlg.wind,
1626  update = True)
1627 
1628  dlg.Destroy()
1629 
1630  self.ZoomHistory(self.Map.region['n'],
1631  self.Map.region['s'],
1632  self.Map.region['e'],
1633  self.Map.region['w'])
1634 
1635  self.UpdateMap()
1636 
1638  """!Save display extents to named region file.
1639  """
1640  dlg = SavedRegion(parent = self,
1641  title = _("Save display extents to region file"),
1642  loadsave='save')
1643 
1644  if dlg.ShowModal() == wx.ID_CANCEL or not dlg.wind:
1645  dlg.Destroy()
1646  return
1647 
1648  # test to see if it already exists and ask permission to overwrite
1649  if grass.find_file(name = dlg.wind, element = 'windows')['name']:
1650  overwrite = wx.MessageBox(parent = self,
1651  message = _("Region file <%s> already exists. "
1652  "Do you want to overwrite it?") % (dlg.wind),
1653  caption = _("Warning"), style = wx.YES_NO | wx.CENTRE)
1654  if (overwrite == wx.YES):
1655  self.SaveRegion(dlg.wind)
1656  else:
1657  self.SaveRegion(dlg.wind)
1658 
1659  dlg.Destroy()
1660 
1661  def SaveRegion(self, wind):
1662  """!Save region settings
1663 
1664  @param wind region name
1665  """
1666  new = self.Map.GetCurrentRegion()
1667 
1668  tmpreg = os.getenv("GRASS_REGION")
1669  if tmpreg:
1670  del os.environ["GRASS_REGION"]
1671 
1672  RunCommand('g.region',
1673  overwrite = True,
1674  parent = self,
1675  flags = 'u',
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  save = wind)
1683 
1684  if tmpreg:
1685  os.environ["GRASS_REGION"] = tmpreg
1686 
1687  def Distance(self, beginpt, endpt, screen = True):
1688  """!Calculete distance
1689 
1690  Ctypes required for LL-locations
1691 
1692  @param beginpt first point
1693  @param endpt second point
1694  @param screen True for screen coordinates otherwise EN
1695  """
1696  if screen:
1697  e1, n1 = self.Pixel2Cell(beginpt)
1698  e2, n2 = self.Pixel2Cell(endpt)
1699  else:
1700  e1, n1 = beginpt
1701  e2, n2 = endpt
1702 
1703  dEast = (e2 - e1)
1704  dNorth = (n2 - n1)
1705 
1706  if self.parent.Map.projinfo['proj'] == 'll' and haveCtypes:
1707  dist = gislib.G_distance(e1, n1, e2, n2)
1708  else:
1709  dist = math.sqrt(math.pow((dEast), 2) + math.pow((dNorth), 2))
1710 
1711  return (dist, (dEast, dNorth))