mapdisp_window.py 62 KB

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