mapdisp_window.py 65 KB

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