mapdisp_window.py 67 KB

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