mapdisp_window.py 67 KB

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