mapwindow.py 60 KB

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