mapdisp_window.py 62 KB

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