mapwindow.py 61 KB

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