mapdisp_window.py 65 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915
  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().gcpmanagement:
  668. # -> georectifier (redraw GCPs)
  669. if self.parent.GetMapToolbar():
  670. if self == self.parent.TgtMapWindow:
  671. coordtype = 'target'
  672. else:
  673. coordtype = 'source'
  674. self.parent.DrawGCP(coordtype)
  675. #
  676. # clear measurement
  677. #
  678. if self.mouse["use"] == "measure":
  679. self.ClearLines(pdc = self.pdcTmp)
  680. self.polycoords = []
  681. self.mouse['use'] = 'pointer'
  682. self.mouse['box'] = 'point'
  683. self.mouse['end'] = [0, 0]
  684. self.SetCursor(self.parent.cursors["default"])
  685. stop = time.clock()
  686. #
  687. # hide process bar
  688. #
  689. self.parent.GetProgressBar().Hide()
  690. #
  691. # update statusbar
  692. #
  693. ### self.Map.SetRegion()
  694. self.parent.StatusbarUpdate()
  695. Debug.msg (1, "BufferedWindow.UpdateMap(): render=%s, renderVector=%s -> time=%g" % \
  696. (render, renderVector, (stop-start)))
  697. return True
  698. def DrawCompRegionExtent(self):
  699. """!Draw computational region extent in the display
  700. Display region is drawn as a blue box inside the computational region,
  701. computational region inside a display region as a red box).
  702. """
  703. if hasattr(self, "regionCoords"):
  704. compReg = self.Map.GetRegion()
  705. dispReg = self.Map.GetCurrentRegion()
  706. reg = None
  707. if self.IsInRegion(dispReg, compReg):
  708. self.polypen = wx.Pen(colour = wx.Colour(0, 0, 255, 128), width = 3, style = wx.SOLID)
  709. reg = dispReg
  710. else:
  711. self.polypen = wx.Pen(colour = wx.Colour(255, 0, 0, 128),
  712. width = 3, style = wx.SOLID)
  713. reg = compReg
  714. self.regionCoords = []
  715. self.regionCoords.append((reg['w'], reg['n']))
  716. self.regionCoords.append((reg['e'], reg['n']))
  717. self.regionCoords.append((reg['e'], reg['s']))
  718. self.regionCoords.append((reg['w'], reg['s']))
  719. self.regionCoords.append((reg['w'], reg['n']))
  720. # draw region extent
  721. self.DrawLines(pdc = self.pdcDec, polycoords = self.regionCoords)
  722. def IsInRegion(self, region, refRegion):
  723. """!
  724. Test if 'region' is inside of 'refRegion'
  725. @param region input region
  726. @param refRegion reference region (e.g. computational region)
  727. @return True if region is inside of refRegion
  728. @return False
  729. """
  730. if region['s'] >= refRegion['s'] and \
  731. region['n'] <= refRegion['n'] and \
  732. region['w'] >= refRegion['w'] and \
  733. region['e'] <= refRegion['e']:
  734. return True
  735. return False
  736. def EraseMap(self):
  737. """!Erase map canvas
  738. """
  739. self.Draw(self.pdc, pdctype = 'clear')
  740. if hasattr(self, "digit"):
  741. self.Draw(self.pdcVector, pdctype = 'clear')
  742. self.Draw(self.pdcDec, pdctype = 'clear')
  743. self.Draw(self.pdcTmp, pdctype = 'clear')
  744. def DragMap(self, moveto):
  745. """!Drag the entire map image for panning.
  746. @param moveto dx,dy
  747. """
  748. dc = wx.BufferedDC(wx.ClientDC(self))
  749. dc.SetBackground(wx.Brush("White"))
  750. dc.Clear()
  751. self.dragimg = wx.DragImage(self.buffer)
  752. self.dragimg.BeginDrag((0, 0), self)
  753. self.dragimg.GetImageRect(moveto)
  754. self.dragimg.Move(moveto)
  755. self.dragimg.DoDrawImage(dc, moveto)
  756. self.dragimg.EndDrag()
  757. def DragItem(self, id, event):
  758. """!Drag an overlay decoration item
  759. """
  760. if id == 99 or id == '' or id == None: return
  761. Debug.msg (5, "BufferedWindow.DragItem(): id=%d" % id)
  762. x, y = self.lastpos
  763. dx = event.GetX() - x
  764. dy = event.GetY() - y
  765. self.pdc.SetBackground(wx.Brush(self.GetBackgroundColour()))
  766. r = self.pdc.GetIdBounds(id)
  767. if type(r) is list:
  768. r = wx.Rect(r[0], r[1], r[2], r[3])
  769. if id > 100: # text dragging
  770. rtop = (r[0],r[1]-r[3],r[2],r[3])
  771. r = r.Union(rtop)
  772. rleft = (r[0]-r[2],r[1],r[2],r[3])
  773. r = r.Union(rleft)
  774. self.pdc.TranslateId(id, dx, dy)
  775. r2 = self.pdc.GetIdBounds(id)
  776. if type(r2) is list:
  777. r2 = wx.Rect(r[0], r[1], r[2], r[3])
  778. if id > 100: # text
  779. self.textdict[id]['bbox'] = r2
  780. self.textdict[id]['coords'][0] += dx
  781. self.textdict[id]['coords'][1] += dy
  782. r = r.Union(r2)
  783. r.Inflate(4,4)
  784. self.RefreshRect(r, False)
  785. self.lastpos = (event.GetX(), event.GetY())
  786. def MouseDraw(self, pdc = None, begin = None, end = None):
  787. """!Mouse box or line from 'begin' to 'end'
  788. If not given from self.mouse['begin'] to self.mouse['end'].
  789. """
  790. if not pdc:
  791. return
  792. if begin is None:
  793. begin = self.mouse['begin']
  794. if end is None:
  795. end = self.mouse['end']
  796. Debug.msg (5, "BufferedWindow.MouseDraw(): use=%s, box=%s, begin=%f,%f, end=%f,%f" % \
  797. (self.mouse['use'], self.mouse['box'],
  798. begin[0], begin[1], end[0], end[1]))
  799. if self.mouse['box'] == "box":
  800. boxid = wx.ID_NEW
  801. mousecoords = [begin[0], begin[1],
  802. end[0], end[1]]
  803. r = pdc.GetIdBounds(boxid)
  804. if type(r) is list:
  805. r = wx.Rect(r[0], r[1], r[2], r[3])
  806. r.Inflate(4, 4)
  807. try:
  808. pdc.ClearId(boxid)
  809. except:
  810. pass
  811. self.RefreshRect(r, False)
  812. pdc.SetId(boxid)
  813. self.Draw(pdc, drawid = boxid, pdctype = 'box', coords = mousecoords)
  814. elif self.mouse['box'] == "line":
  815. self.lineid = wx.ID_NEW
  816. mousecoords = [begin[0], begin[1], \
  817. end[0], end[1]]
  818. x1 = min(begin[0],end[0])
  819. x2 = max(begin[0],end[0])
  820. y1 = min(begin[1],end[1])
  821. y2 = max(begin[1],end[1])
  822. r = wx.Rect(x1,y1,x2-x1,y2-y1)
  823. r.Inflate(4,4)
  824. try:
  825. pdc.ClearId(self.lineid)
  826. except:
  827. pass
  828. self.RefreshRect(r, False)
  829. pdc.SetId(self.lineid)
  830. self.Draw(pdc, drawid = self.lineid, pdctype = 'line', coords = mousecoords)
  831. def DrawLines(self, pdc = None, polycoords = None):
  832. """!Draw polyline in PseudoDC
  833. Set self.pline to wx.NEW_ID + 1
  834. polycoords - list of polyline vertices, geographical coordinates
  835. (if not given, self.polycoords is used)
  836. """
  837. if not pdc:
  838. pdc = self.pdcTmp
  839. if not polycoords:
  840. polycoords = self.polycoords
  841. if len(polycoords) > 0:
  842. self.plineid = wx.ID_NEW + 1
  843. # convert from EN to XY
  844. coords = []
  845. for p in polycoords:
  846. coords.append(self.Cell2Pixel(p))
  847. self.Draw(pdc, drawid = self.plineid, pdctype = 'polyline', coords = coords)
  848. Debug.msg (4, "BufferedWindow.DrawLines(): coords=%s, id=%s" % \
  849. (coords, self.plineid))
  850. return self.plineid
  851. return -1
  852. def DrawCross(self, pdc, coords, size, rotation = 0,
  853. text = None, textAlign = 'lr', textOffset = (5, 5)):
  854. """!Draw cross in PseudoDC
  855. @todo implement rotation
  856. @param pdc PseudoDC
  857. @param coord center coordinates
  858. @param rotation rotate symbol
  859. @param text draw also text (text, font, color, rotation)
  860. @param textAlign alignment (default 'lower-right')
  861. @textOffset offset for text (from center point)
  862. """
  863. Debug.msg(4, "BufferedWindow.DrawCross(): pdc=%s, coords=%s, size=%d" % \
  864. (pdc, coords, size))
  865. coordsCross = ((coords[0] - size, coords[1], coords[0] + size, coords[1]),
  866. (coords[0], coords[1] - size, coords[0], coords[1] + size))
  867. self.lineid = wx.NewId()
  868. for lineCoords in coordsCross:
  869. self.Draw(pdc, drawid = self.lineid, pdctype = 'line', coords = lineCoords)
  870. if not text:
  871. return self.lineid
  872. if textAlign == 'ul':
  873. coord = [coords[0] - textOffset[0], coords[1] - textOffset[1], 0, 0]
  874. elif textAlign == 'ur':
  875. coord = [coords[0] + textOffset[0], coords[1] - textOffset[1], 0, 0]
  876. elif textAlign == 'lr':
  877. coord = [coords[0] + textOffset[0], coords[1] + textOffset[1], 0, 0]
  878. else:
  879. coord = [coords[0] - textOffset[0], coords[1] + textOffset[1], 0, 0]
  880. self.Draw(pdc, img = text,
  881. pdctype = 'text', coords = coord)
  882. return self.lineid
  883. def MouseActions(self, event):
  884. """!Mouse motion and button click notifier
  885. """
  886. if not self.processMouse:
  887. return
  888. # zoom with mouse wheel
  889. if event.GetWheelRotation() != 0:
  890. self.OnMouseWheel(event)
  891. # left mouse button pressed
  892. elif event.LeftDown():
  893. self.OnLeftDown(event)
  894. # left mouse button released
  895. elif event.LeftUp():
  896. self.OnLeftUp(event)
  897. # dragging
  898. elif event.Dragging():
  899. self.OnDragging(event)
  900. # double click
  901. elif event.ButtonDClick():
  902. self.OnButtonDClick(event)
  903. # middle mouse button pressed
  904. elif event.MiddleDown():
  905. self.OnMiddleDown(event)
  906. # middle mouse button relesed
  907. elif event.MiddleUp():
  908. self.OnMiddleUp(event)
  909. # right mouse button pressed
  910. elif event.RightDown():
  911. self.OnRightDown(event)
  912. # right mouse button released
  913. elif event.RightUp():
  914. self.OnRightUp(event)
  915. elif event.Entering():
  916. self.OnMouseEnter(event)
  917. elif event.Moving():
  918. self.OnMouseMoving(event)
  919. def OnMouseWheel(self, event):
  920. """!Mouse wheel moved
  921. """
  922. self.processMouse = False
  923. current = event.GetPositionTuple()[:]
  924. wheel = event.GetWheelRotation()
  925. Debug.msg (5, "BufferedWindow.MouseAction(): wheel=%d" % wheel)
  926. # zoom 1/2 of the screen, centered to current mouse position (TODO: settings)
  927. begin = (current[0] - self.Map.width / 4,
  928. current[1] - self.Map.height / 4)
  929. end = (current[0] + self.Map.width / 4,
  930. current[1] + self.Map.height / 4)
  931. if wheel > 0:
  932. zoomtype = 1
  933. else:
  934. zoomtype = -1
  935. # zoom
  936. self.Zoom(begin, end, zoomtype)
  937. # redraw map
  938. self.UpdateMap()
  939. # update statusbar
  940. self.parent.StatusbarUpdate()
  941. self.Refresh()
  942. self.processMouse = True
  943. def OnDragging(self, event):
  944. """!Mouse dragging
  945. """
  946. Debug.msg (5, "BufferedWindow.MouseAction(): Dragging")
  947. current = event.GetPositionTuple()[:]
  948. previous = self.mouse['begin']
  949. move = (current[0] - previous[0],
  950. current[1] - previous[1])
  951. if hasattr(self, "digit"):
  952. digitToolbar = self.toolbar
  953. else:
  954. digitToolbar = None
  955. # dragging or drawing box with left button
  956. if self.mouse['use'] == 'pan' or \
  957. event.MiddleIsDown():
  958. self.DragMap(move)
  959. # dragging decoration overlay item
  960. elif (self.mouse['use'] == 'pointer' and
  961. not digitToolbar and
  962. self.dragid != None):
  963. self.DragItem(self.dragid, event)
  964. # dragging anything else - rubber band box or line
  965. else:
  966. if (self.mouse['use'] == 'pointer' and
  967. not digitToolbar):
  968. return
  969. self.mouse['end'] = event.GetPositionTuple()[:]
  970. if (event.LeftIsDown() and
  971. not (digitToolbar and
  972. digitToolbar.GetAction() in ("moveLine",) and
  973. self.digit.GetDisplay().GetSelected() > 0)):
  974. self.MouseDraw(pdc = self.pdcTmp)
  975. def OnLeftDown(self, event):
  976. """!Left mouse button pressed
  977. """
  978. Debug.msg (5, "BufferedWindow.OnLeftDown(): use=%s" % \
  979. self.mouse["use"])
  980. self.mouse['begin'] = event.GetPositionTuple()[:]
  981. if self.mouse["use"] in ["measure", "profile"]:
  982. # measure or profile
  983. if len(self.polycoords) == 0:
  984. self.mouse['end'] = self.mouse['begin']
  985. self.polycoords.append(self.Pixel2Cell(self.mouse['begin']))
  986. self.ClearLines(pdc=self.pdcTmp)
  987. else:
  988. self.mouse['begin'] = self.mouse['end']
  989. elif self.mouse['use'] in ('zoom', 'legend'):
  990. pass
  991. # vector digizer
  992. elif self.mouse["use"] == "pointer" and \
  993. hasattr(self, "digit"):
  994. if event.ControlDown():
  995. self.OnLeftDownUndo(event)
  996. else:
  997. self._onLeftDown(event)
  998. elif self.mouse['use'] == 'pointer':
  999. # get decoration or text id
  1000. self.idlist = []
  1001. self.dragid = ''
  1002. self.lastpos = self.mouse['begin']
  1003. idlist = self.pdc.FindObjects(self.lastpos[0], self.lastpos[1],
  1004. self.hitradius)
  1005. if 99 in idlist:
  1006. idlist.remove(99)
  1007. if idlist != []:
  1008. self.dragid = idlist[0] #drag whatever is on top
  1009. else:
  1010. pass
  1011. event.Skip()
  1012. def OnLeftUp(self, event):
  1013. """!Left mouse button released
  1014. """
  1015. Debug.msg (5, "BufferedWindow.OnLeftUp(): use=%s" % \
  1016. self.mouse["use"])
  1017. self.mouse['end'] = event.GetPositionTuple()[:]
  1018. if self.mouse['use'] in ["zoom", "pan"]:
  1019. # set region in zoom or pan
  1020. begin = self.mouse['begin']
  1021. end = self.mouse['end']
  1022. if self.mouse['use'] == 'zoom':
  1023. # set region for click (zero-width box)
  1024. if begin[0] - end[0] == 0 or \
  1025. begin[1] - end[1] == 0:
  1026. # zoom 1/2 of the screen (TODO: settings)
  1027. begin = (end[0] - self.Map.width / 4,
  1028. end[1] - self.Map.height / 4)
  1029. end = (end[0] + self.Map.width / 4,
  1030. end[1] + self.Map.height / 4)
  1031. self.Zoom(begin, end, self.zoomtype)
  1032. # redraw map
  1033. self.UpdateMap(render = True)
  1034. # update statusbar
  1035. self.parent.StatusbarUpdate()
  1036. elif self.mouse["use"] == "query":
  1037. # querying
  1038. layers = self.GetSelectedLayer(multi = True)
  1039. isRaster = False
  1040. nVectors = 0
  1041. for l in layers:
  1042. if l.GetType() == 'raster':
  1043. isRaster = True
  1044. break
  1045. if l.GetType() == 'vector':
  1046. nVectors += 1
  1047. if isRaster or nVectors > 1:
  1048. self.parent.QueryMap(self.mouse['begin'][0],self.mouse['begin'][1])
  1049. else:
  1050. self.parent.QueryVector(self.mouse['begin'][0], self.mouse['begin'][1])
  1051. # clear temp canvas
  1052. self.UpdateMap(render = False, renderVector = False)
  1053. elif self.mouse["use"] == "queryVector":
  1054. # editable mode for vector map layers
  1055. self.parent.QueryVector(self.mouse['begin'][0], self.mouse['begin'][1])
  1056. # clear temp canvas
  1057. self.UpdateMap(render = False, renderVector = False)
  1058. elif self.mouse["use"] in ["measure", "profile"]:
  1059. # measure or profile
  1060. if self.mouse["use"] == "measure":
  1061. self.parent.MeasureDist(self.mouse['begin'], self.mouse['end'])
  1062. self.polycoords.append(self.Pixel2Cell(self.mouse['end']))
  1063. self.ClearLines(pdc = self.pdcTmp)
  1064. self.DrawLines(pdc = self.pdcTmp)
  1065. elif self.mouse["use"] == "pointer" and \
  1066. not self.parent.IsStandalone() and \
  1067. self.parent.GetLayerManager().gcpmanagement:
  1068. # -> GCP manager
  1069. if self.parent.GetToolbar('gcpdisp'):
  1070. coord = self.Pixel2Cell(self.mouse['end'])
  1071. if self.parent.MapWindow == self.parent.SrcMapWindow:
  1072. coordtype = 'source'
  1073. else:
  1074. coordtype = 'target'
  1075. self.parent.GetLayerManager().gcpmanagement.SetGCPData(coordtype, coord, self, confirm = True)
  1076. self.UpdateMap(render = False, renderVector = False)
  1077. elif self.mouse["use"] == "pointer" and \
  1078. hasattr(self, "digit"):
  1079. self._onLeftUp(event)
  1080. elif (self.mouse['use'] == 'pointer' and
  1081. self.dragid >= 0):
  1082. # end drag of overlay decoration
  1083. if self.dragid < 99 and self.dragid in self.overlays:
  1084. self.overlays[self.dragid]['coords'] = self.pdc.GetIdBounds(self.dragid)
  1085. elif self.dragid > 100 and self.dragid in self.textdict:
  1086. self.textdict[self.dragid]['bbox'] = self.pdc.GetIdBounds(self.dragid)
  1087. else:
  1088. pass
  1089. self.dragid = None
  1090. self.currtxtid = None
  1091. elif self.mouse['use'] == 'legend':
  1092. self.ResizeLegend(self.mouse["begin"], self.mouse["end"])
  1093. self.parent.dialogs['legend'].FindWindowByName("resize").SetValue(False)
  1094. self.Map.GetOverlay(1).SetActive(True)
  1095. self.parent.MapWindow.SetCursor(self.parent.cursors["default"])
  1096. self.parent.MapWindow.mouse['use'] = 'pointer'
  1097. self.UpdateMap()
  1098. def OnButtonDClick(self, event):
  1099. """!Mouse button double click
  1100. """
  1101. Debug.msg (5, "BufferedWindow.OnButtonDClick(): use=%s" % \
  1102. self.mouse["use"])
  1103. if self.mouse["use"] == "measure":
  1104. # measure
  1105. self.ClearLines(pdc=self.pdcTmp)
  1106. self.polycoords = []
  1107. self.mouse['use'] = 'pointer'
  1108. self.mouse['box'] = 'point'
  1109. self.mouse['end'] = [0, 0]
  1110. self.Refresh()
  1111. self.SetCursor(self.parent.cursors["default"])
  1112. elif self.mouse["use"] != "profile" or \
  1113. (self.mouse['use'] != 'pointer' and \
  1114. hasattr(self, "digit")):
  1115. # select overlay decoration options dialog
  1116. clickposition = event.GetPositionTuple()[:]
  1117. idlist = self.pdc.FindObjects(clickposition[0], clickposition[1], self.hitradius)
  1118. if idlist == []:
  1119. return
  1120. self.dragid = idlist[0]
  1121. # self.ovlcoords[self.dragid] = self.pdc.GetIdBounds(self.dragid)
  1122. if self.dragid > 100:
  1123. self.currtxtid = self.dragid
  1124. self.parent.OnAddText(None)
  1125. elif self.dragid == 0:
  1126. self.parent.OnAddBarscale(None)
  1127. elif self.dragid == 1:
  1128. self.parent.OnAddLegend(None)
  1129. def OnRightDown(self, event):
  1130. """!Right mouse button pressed
  1131. """
  1132. Debug.msg (5, "BufferedWindow.OnRightDown(): use=%s" % \
  1133. self.mouse["use"])
  1134. if hasattr(self, "digit"):
  1135. self._onRightDown(event)
  1136. event.Skip()
  1137. def OnRightUp(self, event):
  1138. """!Right mouse button released
  1139. """
  1140. Debug.msg (5, "BufferedWindow.OnRightUp(): use=%s" % \
  1141. self.mouse["use"])
  1142. if hasattr(self, "digit"):
  1143. self._onRightUp(event)
  1144. self.redrawAll = True
  1145. self.Refresh()
  1146. event.Skip()
  1147. def OnMiddleDown(self, event):
  1148. """!Middle mouse button pressed
  1149. """
  1150. if not event:
  1151. return
  1152. self.mouse['begin'] = event.GetPositionTuple()[:]
  1153. def OnMiddleUp(self, event):
  1154. """!Middle mouse button released
  1155. """
  1156. self.mouse['end'] = event.GetPositionTuple()[:]
  1157. # set region in zoom or pan
  1158. begin = self.mouse['begin']
  1159. end = self.mouse['end']
  1160. self.Zoom(begin, end, 0) # no zoom
  1161. # redraw map
  1162. self.UpdateMap(render = True)
  1163. # update statusbar
  1164. self.parent.StatusbarUpdate()
  1165. def OnMouseEnter(self, event):
  1166. """!Mouse entered window and no mouse buttons were pressed
  1167. """
  1168. if not self.parent.IsStandalone() and \
  1169. self.parent.GetLayerManager().gcpmanagement:
  1170. if self.parent.GetToolbar('gcpdisp'):
  1171. if not self.parent.MapWindow == self:
  1172. self.parent.MapWindow = self
  1173. self.parent.Map = self.Map
  1174. self.parent.UpdateActive(self)
  1175. # needed for wingrass
  1176. self.SetFocus()
  1177. else:
  1178. event.Skip()
  1179. def OnMouseMoving(self, event):
  1180. """!Motion event and no mouse buttons were pressed
  1181. """
  1182. if self.mouse["use"] == "pointer" and \
  1183. hasattr(self, "digit"):
  1184. self._onMouseMoving(event)
  1185. event.Skip()
  1186. def ClearLines(self, pdc = None):
  1187. """!Clears temporary drawn lines from PseudoDC
  1188. """
  1189. if not pdc:
  1190. pdc = self.pdcTmp
  1191. try:
  1192. pdc.ClearId(self.lineid)
  1193. pdc.RemoveId(self.lineid)
  1194. except:
  1195. pass
  1196. try:
  1197. pdc.ClearId(self.plineid)
  1198. pdc.RemoveId(self.plineid)
  1199. except:
  1200. pass
  1201. Debug.msg(4, "BufferedWindow.ClearLines(): lineid=%s, plineid=%s" %
  1202. (self.lineid, self.plineid))
  1203. return True
  1204. def Pixel2Cell(self, (x, y)):
  1205. """!Convert image coordinates to real word coordinates
  1206. @param x, y image coordinates
  1207. @return easting, northing
  1208. @return None on error
  1209. """
  1210. try:
  1211. x = int(x)
  1212. y = int(y)
  1213. except:
  1214. return None
  1215. if self.Map.region["ewres"] > self.Map.region["nsres"]:
  1216. res = self.Map.region["ewres"]
  1217. else:
  1218. res = self.Map.region["nsres"]
  1219. w = self.Map.region["center_easting"] - (self.Map.width / 2) * res
  1220. n = self.Map.region["center_northing"] + (self.Map.height / 2) * res
  1221. east = w + x * res
  1222. north = n - y * res
  1223. return (east, north)
  1224. def Cell2Pixel(self, (east, north)):
  1225. """!Convert real word coordinates to image coordinates
  1226. """
  1227. try:
  1228. east = float(east)
  1229. north = float(north)
  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. x = (east - w) / res
  1239. y = (n - north) / res
  1240. return (x, y)
  1241. def ResizeLegend(self, begin, end):
  1242. w = abs(begin[0] - end[0])
  1243. h = abs(begin[1] - end[1])
  1244. if begin[0] < end[0]:
  1245. x = begin[0]
  1246. else:
  1247. x = end[0]
  1248. if begin[1] < end[1]:
  1249. y = begin[1]
  1250. else:
  1251. y = end[1]
  1252. screenRect = wx.Rect(x, y, w, h)
  1253. screenSize = self.GetClientSizeTuple()
  1254. at = [(screenSize[1] - (y + h)) / float(screenSize[1]) * 100,
  1255. (screenSize[1] - y) / float(screenSize[1]) * 100,
  1256. x / float(screenSize[0]) * 100,
  1257. (x + w) / float(screenSize[0]) * 100]
  1258. for i, subcmd in enumerate(self.overlays[1]['cmd']):
  1259. if subcmd.startswith('at='):
  1260. self.overlays[1]['cmd'][i] = "at=%d,%d,%d,%d" % (at[0], at[1], at[2], at[3])
  1261. self.Map.ChangeOverlay(1, True, command = self.overlays[1]['cmd'])
  1262. self.overlays[1]['coords'] = (0,0)
  1263. def Zoom(self, begin, end, zoomtype):
  1264. """!Calculates new region while (un)zoom/pan-ing
  1265. """
  1266. x1, y1 = begin
  1267. x2, y2 = end
  1268. newreg = {}
  1269. # threshold - too small squares do not make sense
  1270. # can only zoom to windows of > 5x5 screen pixels
  1271. if abs(x2-x1) > 5 and abs(y2-y1) > 5 and zoomtype != 0:
  1272. if x1 > x2:
  1273. x1, x2 = x2, x1
  1274. if y1 > y2:
  1275. y1, y2 = y2, y1
  1276. # zoom in
  1277. if zoomtype > 0:
  1278. newreg['w'], newreg['n'] = self.Pixel2Cell((x1, y1))
  1279. newreg['e'], newreg['s'] = self.Pixel2Cell((x2, y2))
  1280. # zoom out
  1281. elif zoomtype < 0:
  1282. newreg['w'], newreg['n'] = self.Pixel2Cell((-x1 * 2, -y1 * 2))
  1283. newreg['e'], newreg['s'] = self.Pixel2Cell((self.Map.width + 2 * \
  1284. (self.Map.width - x2),
  1285. self.Map.height + 2 * \
  1286. (self.Map.height - y2)))
  1287. # pan
  1288. elif zoomtype == 0:
  1289. dx = x1 - x2
  1290. dy = y1 - y2
  1291. if dx == 0 and dy == 0:
  1292. dx = x1 - self.Map.width / 2
  1293. dy = y1 - self.Map.height / 2
  1294. newreg['w'], newreg['n'] = self.Pixel2Cell((dx, dy))
  1295. newreg['e'], newreg['s'] = self.Pixel2Cell((self.Map.width + dx,
  1296. self.Map.height + dy))
  1297. # if new region has been calculated, set the values
  1298. if newreg != {}:
  1299. # LL locations
  1300. if self.Map.projinfo['proj'] == 'll':
  1301. self.Map.region['n'] = min(self.Map.region['n'], 90.0)
  1302. self.Map.region['s'] = max(self.Map.region['s'], -90.0)
  1303. ce = newreg['w'] + (newreg['e'] - newreg['w']) / 2
  1304. cn = newreg['s'] + (newreg['n'] - newreg['s']) / 2
  1305. # calculate new center point and display resolution
  1306. self.Map.region['center_easting'] = ce
  1307. self.Map.region['center_northing'] = cn
  1308. self.Map.region['ewres'] = (newreg['e'] - newreg['w']) / self.Map.width
  1309. self.Map.region['nsres'] = (newreg['n'] - newreg['s']) / self.Map.height
  1310. if not self.parent.HasProperty('alignExtent') or \
  1311. self.parent.GetProperty('alignExtent'):
  1312. self.Map.AlignExtentFromDisplay()
  1313. else:
  1314. for k in ('n', 's', 'e', 'w'):
  1315. self.Map.region[k] = newreg[k]
  1316. if hasattr(self, "digit") and \
  1317. hasattr(self, "moveInfo"):
  1318. self._zoom(None)
  1319. self.ZoomHistory(self.Map.region['n'], self.Map.region['s'],
  1320. self.Map.region['e'], self.Map.region['w'])
  1321. if self.redrawAll is False:
  1322. self.redrawAll = True
  1323. def ZoomBack(self):
  1324. """!Zoom to previous extents in zoomhistory list
  1325. """
  1326. zoom = list()
  1327. if len(self.zoomhistory) > 1:
  1328. self.zoomhistory.pop()
  1329. zoom = self.zoomhistory[-1]
  1330. # disable tool if stack is empty
  1331. if len(self.zoomhistory) < 2: # disable tool
  1332. toolbar = self.parent.GetMapToolbar()
  1333. toolbar.Enable('zoomback', enable = False)
  1334. # zoom to selected region
  1335. self.Map.GetRegion(n = zoom[0], s = zoom[1],
  1336. e = zoom[2], w = zoom[3],
  1337. update = True)
  1338. # update map
  1339. self.UpdateMap()
  1340. # update statusbar
  1341. self.parent.StatusbarUpdate()
  1342. def ZoomHistory(self, n, s, e, w):
  1343. """!Manages a list of last 10 zoom extents
  1344. @param n,s,e,w north, south, east, west
  1345. @return removed history item if exists (or None)
  1346. """
  1347. removed = None
  1348. self.zoomhistory.append((n,s,e,w))
  1349. if len(self.zoomhistory) > 10:
  1350. removed = self.zoomhistory.pop(0)
  1351. if removed:
  1352. Debug.msg(4, "BufferedWindow.ZoomHistory(): hist=%s, removed=%s" %
  1353. (self.zoomhistory, removed))
  1354. else:
  1355. Debug.msg(4, "BufferedWindow.ZoomHistory(): hist=%s" %
  1356. (self.zoomhistory))
  1357. # update toolbar
  1358. if len(self.zoomhistory) > 1:
  1359. enable = True
  1360. else:
  1361. enable = False
  1362. toolbar = self.parent.GetMapToolbar()
  1363. toolbar.Enable('zoomback', enable)
  1364. return removed
  1365. def ResetZoomHistory(self):
  1366. """!Reset zoom history"""
  1367. self.zoomhistory = list()
  1368. def ZoomToMap(self, layers = None, ignoreNulls = False, render = True):
  1369. """!Set display extents to match selected raster
  1370. or vector map(s).
  1371. @param layers list of layers to be zoom to
  1372. @param ignoreNulls True to ignore null-values (valid only for rasters)
  1373. @param render True to re-render display
  1374. """
  1375. zoomreg = {}
  1376. if not layers:
  1377. layers = self.GetSelectedLayer(multi = True)
  1378. if not layers:
  1379. return
  1380. rast = []
  1381. vect = []
  1382. updated = False
  1383. for l in layers:
  1384. # only raster/vector layers are currently supported
  1385. if l.type == 'raster':
  1386. rast.append(l.GetName())
  1387. elif l.type == 'vector':
  1388. if hasattr(self, "digit") and \
  1389. self.toolbar.GetLayer() == l:
  1390. w, s, b, e, n, t = self.digit.GetDisplay().GetMapBoundingBox()
  1391. self.Map.GetRegion(n = n, s = s, w = w, e = e,
  1392. update = True)
  1393. updated = True
  1394. else:
  1395. vect.append(l.name)
  1396. elif l.type == 'rgb':
  1397. for rname in l.GetName().splitlines():
  1398. rast.append(rname)
  1399. if not updated:
  1400. self.Map.GetRegion(rast = rast,
  1401. vect = vect,
  1402. update = True)
  1403. self.ZoomHistory(self.Map.region['n'], self.Map.region['s'],
  1404. self.Map.region['e'], self.Map.region['w'])
  1405. if render:
  1406. self.UpdateMap()
  1407. self.parent.StatusbarUpdate()
  1408. def ZoomToWind(self):
  1409. """!Set display geometry to match computational region
  1410. settings (set with g.region)
  1411. """
  1412. self.Map.region = self.Map.GetRegion()
  1413. self.ZoomHistory(self.Map.region['n'], self.Map.region['s'],
  1414. self.Map.region['e'], self.Map.region['w'])
  1415. self.UpdateMap()
  1416. self.parent.StatusbarUpdate()
  1417. def ZoomToDefault(self):
  1418. """!Set display geometry to match default region settings
  1419. """
  1420. self.Map.region = self.Map.GetRegion(default = True)
  1421. self.Map.AdjustRegion() # aling region extent to the display
  1422. self.ZoomHistory(self.Map.region['n'], self.Map.region['s'],
  1423. self.Map.region['e'], self.Map.region['w'])
  1424. self.UpdateMap()
  1425. self.parent.StatusbarUpdate()
  1426. def GoTo(self, e, n):
  1427. region = self.Map.GetCurrentRegion()
  1428. region['center_easting'], region['center_northing'] = e, n
  1429. dn = (region['nsres'] * region['rows']) / 2.
  1430. region['n'] = region['center_northing'] + dn
  1431. region['s'] = region['center_northing'] - dn
  1432. de = (region['ewres'] * region['cols']) / 2.
  1433. region['e'] = region['center_easting'] + de
  1434. region['w'] = region['center_easting'] - de
  1435. self.Map.AdjustRegion()
  1436. # add to zoom history
  1437. self.ZoomHistory(region['n'], region['s'],
  1438. region['e'], region['w'])
  1439. self.UpdateMap()
  1440. def DisplayToWind(self):
  1441. """!Set computational region (WIND file) to match display
  1442. extents
  1443. """
  1444. tmpreg = os.getenv("GRASS_REGION")
  1445. if tmpreg:
  1446. del os.environ["GRASS_REGION"]
  1447. # We ONLY want to set extents here. Don't mess with resolution. Leave that
  1448. # for user to set explicitly with g.region
  1449. new = self.Map.AlignResolution()
  1450. gcmd.RunCommand('g.region',
  1451. parent = self,
  1452. overwrite = True,
  1453. n = new['n'],
  1454. s = new['s'],
  1455. e = new['e'],
  1456. w = new['w'],
  1457. rows = int(new['rows']),
  1458. cols = int(new['cols']))
  1459. if tmpreg:
  1460. os.environ["GRASS_REGION"] = tmpreg
  1461. def ZoomToSaved(self):
  1462. """!Set display geometry to match extents in
  1463. saved region file
  1464. """
  1465. dlg = gdialogs.SavedRegion(parent = self,
  1466. title = _("Zoom to saved region extents"),
  1467. loadsave='load')
  1468. if dlg.ShowModal() == wx.ID_CANCEL or not dlg.wind:
  1469. dlg.Destroy()
  1470. return
  1471. if not grass.find_file(name = dlg.wind, element = 'windows')['name']:
  1472. wx.MessageBox(parent = self,
  1473. message = _("Region <%s> not found. Operation canceled.") % dlg.wind,
  1474. caption = _("Error"), style = wx.ICON_ERROR | wx.OK | wx.CENTRE)
  1475. dlg.Destroy()
  1476. return
  1477. self.Map.GetRegion(regionName = dlg.wind,
  1478. update = True)
  1479. dlg.Destroy()
  1480. self.ZoomHistory(self.Map.region['n'],
  1481. self.Map.region['s'],
  1482. self.Map.region['e'],
  1483. self.Map.region['w'])
  1484. self.UpdateMap()
  1485. def SaveDisplayRegion(self):
  1486. """!Save display extents to named region file.
  1487. """
  1488. dlg = gdialogs.SavedRegion(parent = self,
  1489. title = _("Save display extents to region file"),
  1490. loadsave='save')
  1491. if dlg.ShowModal() == wx.ID_CANCEL or not dlg.wind:
  1492. dlg.Destroy()
  1493. return
  1494. # test to see if it already exists and ask permission to overwrite
  1495. if grass.find_file(name = dlg.wind, element = 'windows')['name']:
  1496. overwrite = wx.MessageBox(parent = self,
  1497. message = _("Region file <%s> already exists. "
  1498. "Do you want to overwrite it?") % (dlg.wind),
  1499. caption = _("Warning"), style = wx.YES_NO | wx.CENTRE)
  1500. if (overwrite == wx.YES):
  1501. self.SaveRegion(dlg.wind)
  1502. else:
  1503. self.SaveRegion(dlg.wind)
  1504. dlg.Destroy()
  1505. def SaveRegion(self, wind):
  1506. """!Save region settings
  1507. @param wind region name
  1508. """
  1509. new = self.Map.GetCurrentRegion()
  1510. tmpreg = os.getenv("GRASS_REGION")
  1511. if tmpreg:
  1512. del os.environ["GRASS_REGION"]
  1513. gcmd.RunCommand('g.region',
  1514. overwrite = True,
  1515. parent = self,
  1516. flags = 'u',
  1517. n = new['n'],
  1518. s = new['s'],
  1519. e = new['e'],
  1520. w = new['w'],
  1521. rows = int(new['rows']),
  1522. cols = int(new['cols']),
  1523. save = wind)
  1524. if tmpreg:
  1525. os.environ["GRASS_REGION"] = tmpreg
  1526. def Distance(self, beginpt, endpt, screen = True):
  1527. """!Calculete distance
  1528. Ctypes required for LL-locations
  1529. @param beginpt first point
  1530. @param endpt second point
  1531. @param screen True for screen coordinates otherwise EN
  1532. """
  1533. if screen:
  1534. e1, n1 = self.Pixel2Cell(beginpt)
  1535. e2, n2 = self.Pixel2Cell(endpt)
  1536. else:
  1537. e1, n1 = beginpt
  1538. e2, n2 = endpt
  1539. dEast = (e2 - e1)
  1540. dNorth = (n2 - n1)
  1541. if self.parent.Map.projinfo['proj'] == 'll' and haveCtypes:
  1542. dist = gislib.G_distance(e1, n1, e2, n2)
  1543. else:
  1544. dist = math.sqrt(math.pow((dEast), 2) + math.pow((dNorth), 2))
  1545. return (dist, (dEast, dNorth))