mapwindow.py 58 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692
  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.Draw(self.pdc, img, drawid = 99)
  366. dc = wx.BufferedPaintDC(self, ibuffer)
  367. dc.Clear()
  368. self.PrepareDC(dc)
  369. self.pdc.DrawToDC(dc)
  370. if self.pdcVector:
  371. self.pdcVector.DrawToDC(dc)
  372. ibuffer.SaveFile(FileName, FileType)
  373. busy.Destroy()
  374. self.UpdateMap(render = True)
  375. self.Refresh()
  376. def GetOverlay(self):
  377. """!Converts rendered overlay files to wx.Image
  378. Updates self.imagedict
  379. @return list of images
  380. """
  381. imgs = []
  382. for overlay in self.Map.GetListOfLayers(l_type = "overlay", l_active = True):
  383. if os.path.isfile(overlay.mapfile) and os.path.getsize(overlay.mapfile):
  384. img = wx.Image(overlay.mapfile, wx.BITMAP_TYPE_ANY)
  385. self.imagedict[img] = { 'id' : overlay.id,
  386. 'layer' : overlay }
  387. imgs.append(img)
  388. return imgs
  389. def GetImage(self):
  390. """!Converts redered map files to wx.Image
  391. Updates self.imagedict (id=99)
  392. @return wx.Image instance (map composition)
  393. """
  394. imgId = 99
  395. if self.mapfile and self.Map.mapfile and os.path.isfile(self.Map.mapfile) and \
  396. os.path.getsize(self.Map.mapfile):
  397. img = wx.Image(self.Map.mapfile, wx.BITMAP_TYPE_ANY)
  398. else:
  399. img = None
  400. self.imagedict[img] = { 'id': imgId }
  401. return img
  402. def UpdateMap(self, render = True, renderVector = True):
  403. """!Updates the canvas anytime there is a change to the
  404. underlaying images or to the geometry of the canvas.
  405. @param render re-render map composition
  406. @param renderVector re-render vector map layer enabled for editing (used for digitizer)
  407. """
  408. start = time.clock()
  409. self.resize = False
  410. if not self.Map.cmdfile and self.img is None:
  411. render = True
  412. #
  413. # initialize process bar (only on 'render')
  414. #
  415. if render or renderVector:
  416. self.parent.GetProgressBar().Show()
  417. if self.parent.GetProgressBar().GetRange() > 0:
  418. self.parent.GetProgressBar().SetValue(1)
  419. #
  420. # render background image if needed
  421. #
  422. # update layer dictionary if there has been a change in layers
  423. if self.tree and self.tree.reorder:
  424. self.tree.ReorderLayers()
  425. # reset flag for auto-rendering
  426. if self.tree:
  427. self.tree.rerender = False
  428. try:
  429. if render:
  430. # update display size
  431. self.Map.ChangeMapSize(self.GetClientSize())
  432. if self.parent.GetProperty('resolution'):
  433. # use computation region resolution for rendering
  434. windres = True
  435. else:
  436. windres = False
  437. self.mapfile = self.Map.Render(force = True, mapWindow = self.parent,
  438. windres = windres)
  439. else:
  440. self.mapfile = self.Map.Render(force = False, mapWindow = self.parent)
  441. except GException, e:
  442. GError(message = e.value)
  443. self.mapfile = None
  444. self.img = self.GetImage() # id=99
  445. #
  446. # clear pseudoDcs
  447. #
  448. for pdc in (self.pdc,
  449. self.pdcDec,
  450. self.pdcTmp):
  451. pdc.Clear()
  452. pdc.RemoveAll()
  453. #
  454. # draw background map image to PseudoDC
  455. #
  456. if not self.img:
  457. self.Draw(self.pdc, pdctype = 'clear')
  458. else:
  459. try:
  460. id = self.imagedict[self.img]['id']
  461. except:
  462. return False
  463. self.Draw(self.pdc, self.img, drawid = id)
  464. #
  465. # render vector map layer
  466. #
  467. if renderVector and hasattr(self, "digit"):
  468. self._updateMap()
  469. #
  470. # render overlays
  471. #
  472. for img in self.GetOverlay():
  473. # draw any active and defined overlays
  474. if self.imagedict[img]['layer'].IsActive():
  475. id = self.imagedict[img]['id']
  476. self.Draw(self.pdc, img = img, drawid = id,
  477. pdctype = self.overlays[id]['pdcType'], coords = self.overlays[id]['coords'])
  478. for id in self.textdict.keys():
  479. self.Draw(self.pdc, img = self.textdict[id], drawid = id,
  480. pdctype = 'text', coords = [10, 10, 10, 10])
  481. # optionally draw computational extent box
  482. self.DrawCompRegionExtent()
  483. #
  484. # redraw pdcTmp if needed
  485. #
  486. if len(self.polycoords) > 0:
  487. self.DrawLines(self.pdcTmp)
  488. if not self.parent.IsStandalone() and \
  489. self.parent.GetLayerManager().gcpmanagement:
  490. # -> georectifier (redraw GCPs)
  491. if self.parent.GetMapToolbar():
  492. if self == self.parent.TgtMapWindow:
  493. coordtype = 'target'
  494. else:
  495. coordtype = 'source'
  496. self.parent.DrawGCP(coordtype)
  497. #
  498. # clear measurement
  499. #
  500. if self.mouse["use"] == "measure":
  501. self.ClearLines(pdc = self.pdcTmp)
  502. self.polycoords = []
  503. self.mouse['use'] = 'pointer'
  504. self.mouse['box'] = 'point'
  505. self.mouse['end'] = [0, 0]
  506. self.SetCursor(self.parent.cursors["default"])
  507. stop = time.clock()
  508. #
  509. # hide process bar
  510. #
  511. self.parent.GetProgressBar().Hide()
  512. #
  513. # update statusbar
  514. #
  515. ### self.Map.SetRegion()
  516. self.parent.StatusbarUpdate()
  517. Debug.msg (1, "BufferedWindow.UpdateMap(): render=%s, renderVector=%s -> time=%g" % \
  518. (render, renderVector, (stop-start)))
  519. return True
  520. def DrawCompRegionExtent(self):
  521. """!Draw computational region extent in the display
  522. Display region is drawn as a blue box inside the computational region,
  523. computational region inside a display region as a red box).
  524. """
  525. if hasattr(self, "regionCoords"):
  526. compReg = self.Map.GetRegion()
  527. dispReg = self.Map.GetCurrentRegion()
  528. reg = None
  529. if self.IsInRegion(dispReg, compReg):
  530. self.polypen = wx.Pen(colour = wx.Colour(0, 0, 255, 128), width = 3, style = wx.SOLID)
  531. reg = dispReg
  532. else:
  533. self.polypen = wx.Pen(colour = wx.Colour(255, 0, 0, 128),
  534. width = 3, style = wx.SOLID)
  535. reg = compReg
  536. self.regionCoords = []
  537. self.regionCoords.append((reg['w'], reg['n']))
  538. self.regionCoords.append((reg['e'], reg['n']))
  539. self.regionCoords.append((reg['e'], reg['s']))
  540. self.regionCoords.append((reg['w'], reg['s']))
  541. self.regionCoords.append((reg['w'], reg['n']))
  542. # draw region extent
  543. self.DrawLines(pdc = self.pdcDec, polycoords = self.regionCoords)
  544. def IsInRegion(self, region, refRegion):
  545. """!
  546. Test if 'region' is inside of 'refRegion'
  547. @param region input region
  548. @param refRegion reference region (e.g. computational region)
  549. @return True if region is inside of refRegion
  550. @return False
  551. """
  552. if region['s'] >= refRegion['s'] and \
  553. region['n'] <= refRegion['n'] and \
  554. region['w'] >= refRegion['w'] and \
  555. region['e'] <= refRegion['e']:
  556. return True
  557. return False
  558. def EraseMap(self):
  559. """!Erase map canvas
  560. """
  561. self.Draw(self.pdc, pdctype = 'clear')
  562. if hasattr(self, "digit"):
  563. self.Draw(self.pdcVector, pdctype = 'clear')
  564. self.Draw(self.pdcDec, pdctype = 'clear')
  565. self.Draw(self.pdcTmp, pdctype = 'clear')
  566. def DragMap(self, moveto):
  567. """!Drag the entire map image for panning.
  568. @param moveto dx,dy
  569. """
  570. dc = wx.BufferedDC(wx.ClientDC(self))
  571. dc.SetBackground(wx.Brush("White"))
  572. dc.Clear()
  573. self.dragimg = wx.DragImage(self.buffer)
  574. self.dragimg.BeginDrag((0, 0), self)
  575. self.dragimg.GetImageRect(moveto)
  576. self.dragimg.Move(moveto)
  577. self.dragimg.DoDrawImage(dc, moveto)
  578. self.dragimg.EndDrag()
  579. def DragItem(self, id, event):
  580. """!Drag an overlay decoration item
  581. """
  582. if id == 99 or id == '' or id == None: return
  583. Debug.msg (5, "BufferedWindow.DragItem(): id=%d" % id)
  584. x, y = self.lastpos
  585. dx = event.GetX() - x
  586. dy = event.GetY() - y
  587. self.pdc.SetBackground(wx.Brush(self.GetBackgroundColour()))
  588. r = self.pdc.GetIdBounds(id)
  589. if type(r) is list:
  590. r = wx.Rect(r[0], r[1], r[2], r[3])
  591. if id > 100: # text dragging
  592. rtop = (r[0],r[1]-r[3],r[2],r[3])
  593. r = r.Union(rtop)
  594. rleft = (r[0]-r[2],r[1],r[2],r[3])
  595. r = r.Union(rleft)
  596. self.pdc.TranslateId(id, dx, dy)
  597. r2 = self.pdc.GetIdBounds(id)
  598. if type(r2) is list:
  599. r2 = wx.Rect(r[0], r[1], r[2], r[3])
  600. if id > 100: # text
  601. self.textdict[id]['bbox'] = r2
  602. self.textdict[id]['coords'][0] += dx
  603. self.textdict[id]['coords'][1] += dy
  604. r = r.Union(r2)
  605. r.Inflate(4,4)
  606. self.RefreshRect(r, False)
  607. self.lastpos = (event.GetX(), event.GetY())
  608. def MouseDraw(self, pdc = None, begin = None, end = None):
  609. """!Mouse box or line from 'begin' to 'end'
  610. If not given from self.mouse['begin'] to self.mouse['end'].
  611. """
  612. if not pdc:
  613. return
  614. if begin is None:
  615. begin = self.mouse['begin']
  616. if end is None:
  617. end = self.mouse['end']
  618. Debug.msg (5, "BufferedWindow.MouseDraw(): use=%s, box=%s, begin=%f,%f, end=%f,%f" % \
  619. (self.mouse['use'], self.mouse['box'],
  620. begin[0], begin[1], end[0], end[1]))
  621. if self.mouse['box'] == "box":
  622. boxid = wx.ID_NEW
  623. mousecoords = [begin[0], begin[1],
  624. end[0], end[1]]
  625. r = pdc.GetIdBounds(boxid)
  626. if type(r) is list:
  627. r = wx.Rect(r[0], r[1], r[2], r[3])
  628. r.Inflate(4, 4)
  629. try:
  630. pdc.ClearId(boxid)
  631. except:
  632. pass
  633. self.RefreshRect(r, False)
  634. pdc.SetId(boxid)
  635. self.Draw(pdc, drawid = boxid, pdctype = 'box', coords = mousecoords)
  636. elif self.mouse['box'] == "line":
  637. self.lineid = wx.ID_NEW
  638. mousecoords = [begin[0], begin[1], \
  639. end[0], end[1]]
  640. x1 = min(begin[0],end[0])
  641. x2 = max(begin[0],end[0])
  642. y1 = min(begin[1],end[1])
  643. y2 = max(begin[1],end[1])
  644. r = wx.Rect(x1,y1,x2-x1,y2-y1)
  645. r.Inflate(4,4)
  646. try:
  647. pdc.ClearId(self.lineid)
  648. except:
  649. pass
  650. self.RefreshRect(r, False)
  651. pdc.SetId(self.lineid)
  652. self.Draw(pdc, drawid = self.lineid, pdctype = 'line', coords = mousecoords)
  653. def DrawLines(self, pdc = None, polycoords = None):
  654. """!Draw polyline in PseudoDC
  655. Set self.pline to wx.NEW_ID + 1
  656. polycoords - list of polyline vertices, geographical coordinates
  657. (if not given, self.polycoords is used)
  658. """
  659. if not pdc:
  660. pdc = self.pdcTmp
  661. if not polycoords:
  662. polycoords = self.polycoords
  663. if len(polycoords) > 0:
  664. self.plineid = wx.ID_NEW + 1
  665. # convert from EN to XY
  666. coords = []
  667. for p in polycoords:
  668. coords.append(self.Cell2Pixel(p))
  669. self.Draw(pdc, drawid = self.plineid, pdctype = 'polyline', coords = coords)
  670. Debug.msg (4, "BufferedWindow.DrawLines(): coords=%s, id=%s" % \
  671. (coords, self.plineid))
  672. return self.plineid
  673. return -1
  674. def DrawCross(self, pdc, coords, size, rotation = 0,
  675. text = None, textAlign = 'lr', textOffset = (5, 5)):
  676. """!Draw cross in PseudoDC
  677. @todo implement rotation
  678. @param pdc PseudoDC
  679. @param coord center coordinates
  680. @param rotation rotate symbol
  681. @param text draw also text (text, font, color, rotation)
  682. @param textAlign alignment (default 'lower-right')
  683. @textOffset offset for text (from center point)
  684. """
  685. Debug.msg(4, "BufferedWindow.DrawCross(): pdc=%s, coords=%s, size=%d" % \
  686. (pdc, coords, size))
  687. coordsCross = ((coords[0] - size, coords[1], coords[0] + size, coords[1]),
  688. (coords[0], coords[1] - size, coords[0], coords[1] + size))
  689. self.lineid = wx.NewId()
  690. for lineCoords in coordsCross:
  691. self.Draw(pdc, drawid = self.lineid, pdctype = 'line', coords = lineCoords)
  692. if not text:
  693. return self.lineid
  694. if textAlign == 'ul':
  695. coord = [coords[0] - textOffset[0], coords[1] - textOffset[1], 0, 0]
  696. elif textAlign == 'ur':
  697. coord = [coords[0] + textOffset[0], coords[1] - textOffset[1], 0, 0]
  698. elif textAlign == 'lr':
  699. coord = [coords[0] + textOffset[0], coords[1] + textOffset[1], 0, 0]
  700. else:
  701. coord = [coords[0] - textOffset[0], coords[1] + textOffset[1], 0, 0]
  702. self.Draw(pdc, img = text,
  703. pdctype = 'text', coords = coord)
  704. return self.lineid
  705. def MouseActions(self, event):
  706. """!Mouse motion and button click notifier
  707. """
  708. if not self.processMouse:
  709. return
  710. # zoom with mouse wheel
  711. if event.GetWheelRotation() != 0:
  712. self.OnMouseWheel(event)
  713. # left mouse button pressed
  714. elif event.LeftDown():
  715. self.OnLeftDown(event)
  716. # left mouse button released
  717. elif event.LeftUp():
  718. self.OnLeftUp(event)
  719. # dragging
  720. elif event.Dragging():
  721. self.OnDragging(event)
  722. # double click
  723. elif event.ButtonDClick():
  724. self.OnButtonDClick(event)
  725. # middle mouse button pressed
  726. elif event.MiddleDown():
  727. self.OnMiddleDown(event)
  728. # middle mouse button relesed
  729. elif event.MiddleUp():
  730. self.OnMiddleUp(event)
  731. # right mouse button pressed
  732. elif event.RightDown():
  733. self.OnRightDown(event)
  734. # right mouse button released
  735. elif event.RightUp():
  736. self.OnRightUp(event)
  737. elif event.Entering():
  738. self.OnMouseEnter(event)
  739. elif event.Moving():
  740. self.OnMouseMoving(event)
  741. def OnMouseWheel(self, event):
  742. """!Mouse wheel moved
  743. """
  744. self.processMouse = False
  745. current = event.GetPositionTuple()[:]
  746. wheel = event.GetWheelRotation()
  747. Debug.msg (5, "BufferedWindow.MouseAction(): wheel=%d" % wheel)
  748. # zoom 1/2 of the screen, centered to current mouse position (TODO: settings)
  749. begin = (current[0] - self.Map.width / 4,
  750. current[1] - self.Map.height / 4)
  751. end = (current[0] + self.Map.width / 4,
  752. current[1] + self.Map.height / 4)
  753. if wheel > 0:
  754. zoomtype = 1
  755. else:
  756. zoomtype = -1
  757. # zoom
  758. self.Zoom(begin, end, zoomtype)
  759. # redraw map
  760. self.UpdateMap()
  761. # update statusbar
  762. self.parent.StatusbarUpdate()
  763. self.Refresh()
  764. self.processMouse = True
  765. def OnDragging(self, event):
  766. """!Mouse dragging
  767. """
  768. Debug.msg (5, "BufferedWindow.MouseAction(): Dragging")
  769. current = event.GetPositionTuple()[:]
  770. previous = self.mouse['begin']
  771. move = (current[0] - previous[0],
  772. current[1] - previous[1])
  773. if hasattr(self, "digit"):
  774. digitToolbar = self.toolbar
  775. else:
  776. digitToolbar = None
  777. # dragging or drawing box with left button
  778. if self.mouse['use'] == 'pan' or \
  779. event.MiddleIsDown():
  780. self.DragMap(move)
  781. # dragging decoration overlay item
  782. elif (self.mouse['use'] == 'pointer' and
  783. not digitToolbar and
  784. self.dragid != None):
  785. self.DragItem(self.dragid, event)
  786. # dragging anything else - rubber band box or line
  787. else:
  788. if (self.mouse['use'] == 'pointer' and
  789. not digitToolbar):
  790. return
  791. self.mouse['end'] = event.GetPositionTuple()[:]
  792. if (event.LeftIsDown() and
  793. not (digitToolbar and
  794. digitToolbar.GetAction() in ("moveLine",) and
  795. self.digit.GetDisplay().GetSelected() > 0)):
  796. self.MouseDraw(pdc = self.pdcTmp)
  797. def OnLeftDown(self, event):
  798. """!Left mouse button pressed
  799. """
  800. Debug.msg (5, "BufferedWindow.OnLeftDown(): use=%s" % \
  801. self.mouse["use"])
  802. self.mouse['begin'] = event.GetPositionTuple()[:]
  803. if self.mouse["use"] in ["measure", "profile"]:
  804. # measure or profile
  805. if len(self.polycoords) == 0:
  806. self.mouse['end'] = self.mouse['begin']
  807. self.polycoords.append(self.Pixel2Cell(self.mouse['begin']))
  808. self.ClearLines(pdc=self.pdcTmp)
  809. else:
  810. self.mouse['begin'] = self.mouse['end']
  811. elif self.mouse['use'] in ('zoom', 'legend'):
  812. pass
  813. # vector digizer
  814. elif self.mouse["use"] == "pointer" and \
  815. hasattr(self, "digit"):
  816. if event.ControlDown():
  817. self.OnLeftDownUndo(event)
  818. else:
  819. self._onLeftDown(event)
  820. elif self.mouse['use'] == 'pointer':
  821. # get decoration or text id
  822. self.idlist = []
  823. self.dragid = ''
  824. self.lastpos = self.mouse['begin']
  825. idlist = self.pdc.FindObjects(self.lastpos[0], self.lastpos[1],
  826. self.hitradius)
  827. if 99 in idlist:
  828. idlist.remove(99)
  829. if idlist != []:
  830. self.dragid = idlist[0] #drag whatever is on top
  831. else:
  832. pass
  833. event.Skip()
  834. def OnLeftUp(self, event):
  835. """!Left mouse button released
  836. """
  837. Debug.msg (5, "BufferedWindow.OnLeftUp(): use=%s" % \
  838. self.mouse["use"])
  839. self.mouse['end'] = event.GetPositionTuple()[:]
  840. if self.mouse['use'] in ["zoom", "pan"]:
  841. # set region in zoom or pan
  842. begin = self.mouse['begin']
  843. end = self.mouse['end']
  844. if self.mouse['use'] == 'zoom':
  845. # set region for click (zero-width box)
  846. if begin[0] - end[0] == 0 or \
  847. begin[1] - end[1] == 0:
  848. # zoom 1/2 of the screen (TODO: settings)
  849. begin = (end[0] - self.Map.width / 4,
  850. end[1] - self.Map.height / 4)
  851. end = (end[0] + self.Map.width / 4,
  852. end[1] + self.Map.height / 4)
  853. self.Zoom(begin, end, self.zoomtype)
  854. # redraw map
  855. self.UpdateMap(render = True)
  856. # update statusbar
  857. self.parent.StatusbarUpdate()
  858. elif self.mouse["use"] == "query":
  859. # querying
  860. layers = self.GetSelectedLayer(multi = True)
  861. isRaster = False
  862. nVectors = 0
  863. for l in layers:
  864. if l.GetType() == 'raster':
  865. isRaster = True
  866. break
  867. if l.GetType() == 'vector':
  868. nVectors += 1
  869. if isRaster or nVectors > 1:
  870. self.parent.QueryMap(self.mouse['begin'][0],self.mouse['begin'][1])
  871. else:
  872. self.parent.QueryVector(self.mouse['begin'][0], self.mouse['begin'][1])
  873. # clear temp canvas
  874. self.UpdateMap(render = False, renderVector = False)
  875. elif self.mouse["use"] == "queryVector":
  876. # editable mode for vector map layers
  877. self.parent.QueryVector(self.mouse['begin'][0], self.mouse['begin'][1])
  878. # clear temp canvas
  879. self.UpdateMap(render = False, renderVector = False)
  880. elif self.mouse["use"] in ["measure", "profile"]:
  881. # measure or profile
  882. if self.mouse["use"] == "measure":
  883. self.parent.MeasureDist(self.mouse['begin'], self.mouse['end'])
  884. self.polycoords.append(self.Pixel2Cell(self.mouse['end']))
  885. self.ClearLines(pdc = self.pdcTmp)
  886. self.DrawLines(pdc = self.pdcTmp)
  887. elif self.mouse["use"] == "pointer" and \
  888. not self.parent.IsStandalone() and \
  889. self.parent.GetLayerManager().gcpmanagement:
  890. # -> GCP manager
  891. if self.parent.GetToolbar('gcpdisp'):
  892. coord = self.Pixel2Cell(self.mouse['end'])
  893. if self.parent.MapWindow == self.parent.SrcMapWindow:
  894. coordtype = 'source'
  895. else:
  896. coordtype = 'target'
  897. self.parent.GetLayerManager().gcpmanagement.SetGCPData(coordtype, coord, self, confirm = True)
  898. self.UpdateMap(render = False, renderVector = False)
  899. elif self.mouse["use"] == "pointer" and \
  900. hasattr(self, "digit"):
  901. self._onLeftUp(event)
  902. elif (self.mouse['use'] == 'pointer' and
  903. self.dragid >= 0):
  904. # end drag of overlay decoration
  905. if self.dragid < 99 and self.dragid in self.overlays:
  906. self.overlays[self.dragid]['coords'] = self.pdc.GetIdBounds(self.dragid)
  907. elif self.dragid > 100 and self.dragid in self.textdict:
  908. self.textdict[self.dragid]['bbox'] = self.pdc.GetIdBounds(self.dragid)
  909. else:
  910. pass
  911. self.dragid = None
  912. self.currtxtid = None
  913. elif self.mouse['use'] == 'legend':
  914. self.ResizeLegend(self.mouse["begin"], self.mouse["end"])
  915. self.parent.dialogs['legend'].FindWindowByName("resize").SetValue(False)
  916. self.Map.GetOverlay(1).SetActive(True)
  917. self.parent.MapWindow.SetCursor(self.parent.cursors["default"])
  918. self.parent.MapWindow.mouse['use'] = 'pointer'
  919. self.UpdateMap()
  920. def OnButtonDClick(self, event):
  921. """!Mouse button double click
  922. """
  923. Debug.msg (5, "BufferedWindow.OnButtonDClick(): use=%s" % \
  924. self.mouse["use"])
  925. if self.mouse["use"] == "measure":
  926. # measure
  927. self.ClearLines(pdc=self.pdcTmp)
  928. self.polycoords = []
  929. self.mouse['use'] = 'pointer'
  930. self.mouse['box'] = 'point'
  931. self.mouse['end'] = [0, 0]
  932. self.Refresh()
  933. self.SetCursor(self.parent.cursors["default"])
  934. elif self.mouse["use"] != "profile" or \
  935. (self.mouse['use'] != 'pointer' and \
  936. hasattr(self, "digit")):
  937. # select overlay decoration options dialog
  938. clickposition = event.GetPositionTuple()[:]
  939. idlist = self.pdc.FindObjects(clickposition[0], clickposition[1], self.hitradius)
  940. if idlist == []:
  941. return
  942. self.dragid = idlist[0]
  943. # self.ovlcoords[self.dragid] = self.pdc.GetIdBounds(self.dragid)
  944. if self.dragid > 100:
  945. self.currtxtid = self.dragid
  946. self.parent.OnAddText(None)
  947. elif self.dragid == 0:
  948. self.parent.OnAddBarscale(None)
  949. elif self.dragid == 1:
  950. self.parent.OnAddLegend(None)
  951. def OnRightDown(self, event):
  952. """!Right mouse button pressed
  953. """
  954. Debug.msg (5, "BufferedWindow.OnRightDown(): use=%s" % \
  955. self.mouse["use"])
  956. if hasattr(self, "digit"):
  957. self._onRightDown(event)
  958. event.Skip()
  959. def OnRightUp(self, event):
  960. """!Right mouse button released
  961. """
  962. Debug.msg (5, "BufferedWindow.OnRightUp(): use=%s" % \
  963. self.mouse["use"])
  964. if hasattr(self, "digit"):
  965. self._onRightUp(event)
  966. self.redrawAll = True
  967. self.Refresh()
  968. event.Skip()
  969. def OnMiddleDown(self, event):
  970. """!Middle mouse button pressed
  971. """
  972. if not event:
  973. return
  974. self.mouse['begin'] = event.GetPositionTuple()[:]
  975. def OnMiddleUp(self, event):
  976. """!Middle mouse button released
  977. """
  978. self.mouse['end'] = event.GetPositionTuple()[:]
  979. # set region in zoom or pan
  980. begin = self.mouse['begin']
  981. end = self.mouse['end']
  982. self.Zoom(begin, end, 0) # no zoom
  983. # redraw map
  984. self.UpdateMap(render = True)
  985. # update statusbar
  986. self.parent.StatusbarUpdate()
  987. def OnMouseEnter(self, event):
  988. """!Mouse entered window and no mouse buttons were pressed
  989. """
  990. if not self.parent.IsStandalone() and \
  991. self.parent.GetLayerManager().gcpmanagement:
  992. if self.parent.GetToolbar('gcpdisp'):
  993. if not self.parent.MapWindow == self:
  994. self.parent.MapWindow = self
  995. self.parent.Map = self.Map
  996. self.parent.UpdateActive(self)
  997. # needed for wingrass
  998. self.SetFocus()
  999. else:
  1000. event.Skip()
  1001. def OnMouseMoving(self, event):
  1002. """!Motion event and no mouse buttons were pressed
  1003. """
  1004. if self.mouse["use"] == "pointer" and \
  1005. hasattr(self, "digit"):
  1006. self._onMouseMoving(event)
  1007. event.Skip()
  1008. def ClearLines(self, pdc = None):
  1009. """!Clears temporary drawn lines from PseudoDC
  1010. """
  1011. if not pdc:
  1012. pdc = self.pdcTmp
  1013. try:
  1014. pdc.ClearId(self.lineid)
  1015. pdc.RemoveId(self.lineid)
  1016. except:
  1017. pass
  1018. try:
  1019. pdc.ClearId(self.plineid)
  1020. pdc.RemoveId(self.plineid)
  1021. except:
  1022. pass
  1023. Debug.msg(4, "BufferedWindow.ClearLines(): lineid=%s, plineid=%s" %
  1024. (self.lineid, self.plineid))
  1025. return True
  1026. def Pixel2Cell(self, (x, y)):
  1027. """!Convert image coordinates to real word coordinates
  1028. @param x, y image coordinates
  1029. @return easting, northing
  1030. @return None on error
  1031. """
  1032. try:
  1033. x = int(x)
  1034. y = int(y)
  1035. except:
  1036. return None
  1037. if self.Map.region["ewres"] > self.Map.region["nsres"]:
  1038. res = self.Map.region["ewres"]
  1039. else:
  1040. res = self.Map.region["nsres"]
  1041. w = self.Map.region["center_easting"] - (self.Map.width / 2) * res
  1042. n = self.Map.region["center_northing"] + (self.Map.height / 2) * res
  1043. east = w + x * res
  1044. north = n - y * res
  1045. return (east, north)
  1046. def Cell2Pixel(self, (east, north)):
  1047. """!Convert real word coordinates to image coordinates
  1048. """
  1049. try:
  1050. east = float(east)
  1051. north = float(north)
  1052. except:
  1053. return None
  1054. if self.Map.region["ewres"] > self.Map.region["nsres"]:
  1055. res = self.Map.region["ewres"]
  1056. else:
  1057. res = self.Map.region["nsres"]
  1058. w = self.Map.region["center_easting"] - (self.Map.width / 2) * res
  1059. n = self.Map.region["center_northing"] + (self.Map.height / 2) * res
  1060. x = (east - w) / res
  1061. y = (n - north) / res
  1062. return (x, y)
  1063. def ResizeLegend(self, begin, end):
  1064. w = abs(begin[0] - end[0])
  1065. h = abs(begin[1] - end[1])
  1066. if begin[0] < end[0]:
  1067. x = begin[0]
  1068. else:
  1069. x = end[0]
  1070. if begin[1] < end[1]:
  1071. y = begin[1]
  1072. else:
  1073. y = end[1]
  1074. screenRect = wx.Rect(x, y, w, h)
  1075. screenSize = self.GetClientSizeTuple()
  1076. at = [(screenSize[1] - (y + h)) / float(screenSize[1]) * 100,
  1077. (screenSize[1] - y) / float(screenSize[1]) * 100,
  1078. x / float(screenSize[0]) * 100,
  1079. (x + w) / float(screenSize[0]) * 100]
  1080. for i, subcmd in enumerate(self.overlays[1]['cmd']):
  1081. if subcmd.startswith('at='):
  1082. self.overlays[1]['cmd'][i] = "at=%d,%d,%d,%d" % (at[0], at[1], at[2], at[3])
  1083. self.Map.ChangeOverlay(1, True, command = self.overlays[1]['cmd'])
  1084. self.overlays[1]['coords'] = (0,0)
  1085. def Zoom(self, begin, end, zoomtype):
  1086. """!Calculates new region while (un)zoom/pan-ing
  1087. """
  1088. x1, y1 = begin
  1089. x2, y2 = end
  1090. newreg = {}
  1091. # threshold - too small squares do not make sense
  1092. # can only zoom to windows of > 5x5 screen pixels
  1093. if abs(x2-x1) > 5 and abs(y2-y1) > 5 and zoomtype != 0:
  1094. if x1 > x2:
  1095. x1, x2 = x2, x1
  1096. if y1 > y2:
  1097. y1, y2 = y2, y1
  1098. # zoom in
  1099. if zoomtype > 0:
  1100. newreg['w'], newreg['n'] = self.Pixel2Cell((x1, y1))
  1101. newreg['e'], newreg['s'] = self.Pixel2Cell((x2, y2))
  1102. # zoom out
  1103. elif zoomtype < 0:
  1104. newreg['w'], newreg['n'] = self.Pixel2Cell((-x1 * 2, -y1 * 2))
  1105. newreg['e'], newreg['s'] = self.Pixel2Cell((self.Map.width + 2 * \
  1106. (self.Map.width - x2),
  1107. self.Map.height + 2 * \
  1108. (self.Map.height - y2)))
  1109. # pan
  1110. elif zoomtype == 0:
  1111. dx = x1 - x2
  1112. dy = y1 - y2
  1113. if dx == 0 and dy == 0:
  1114. dx = x1 - self.Map.width / 2
  1115. dy = y1 - self.Map.height / 2
  1116. newreg['w'], newreg['n'] = self.Pixel2Cell((dx, dy))
  1117. newreg['e'], newreg['s'] = self.Pixel2Cell((self.Map.width + dx,
  1118. self.Map.height + dy))
  1119. # if new region has been calculated, set the values
  1120. if newreg != {}:
  1121. # LL locations
  1122. if self.Map.projinfo['proj'] == 'll':
  1123. self.Map.region['n'] = min(self.Map.region['n'], 90.0)
  1124. self.Map.region['s'] = max(self.Map.region['s'], -90.0)
  1125. ce = newreg['w'] + (newreg['e'] - newreg['w']) / 2
  1126. cn = newreg['s'] + (newreg['n'] - newreg['s']) / 2
  1127. # calculate new center point and display resolution
  1128. self.Map.region['center_easting'] = ce
  1129. self.Map.region['center_northing'] = cn
  1130. self.Map.region['ewres'] = (newreg['e'] - newreg['w']) / self.Map.width
  1131. self.Map.region['nsres'] = (newreg['n'] - newreg['s']) / self.Map.height
  1132. if not self.parent.HasProperty('alignExtent') or \
  1133. self.parent.GetProperty('alignExtent'):
  1134. self.Map.AlignExtentFromDisplay()
  1135. else:
  1136. for k in ('n', 's', 'e', 'w'):
  1137. self.Map.region[k] = newreg[k]
  1138. if hasattr(self, "digit") and \
  1139. hasattr(self, "moveInfo"):
  1140. self._zoom(None)
  1141. self.ZoomHistory(self.Map.region['n'], self.Map.region['s'],
  1142. self.Map.region['e'], self.Map.region['w'])
  1143. if self.redrawAll is False:
  1144. self.redrawAll = True
  1145. def ZoomBack(self):
  1146. """!Zoom to previous extents in zoomhistory list
  1147. """
  1148. zoom = list()
  1149. if len(self.zoomhistory) > 1:
  1150. self.zoomhistory.pop()
  1151. zoom = self.zoomhistory[-1]
  1152. # disable tool if stack is empty
  1153. if len(self.zoomhistory) < 2: # disable tool
  1154. toolbar = self.parent.GetMapToolbar()
  1155. toolbar.Enable('zoomback', enable = False)
  1156. # zoom to selected region
  1157. self.Map.GetRegion(n = zoom[0], s = zoom[1],
  1158. e = zoom[2], w = zoom[3],
  1159. update = True)
  1160. # update map
  1161. self.UpdateMap()
  1162. # update statusbar
  1163. self.parent.StatusbarUpdate()
  1164. def ZoomHistory(self, n, s, e, w):
  1165. """!Manages a list of last 10 zoom extents
  1166. @param n,s,e,w north, south, east, west
  1167. @return removed history item if exists (or None)
  1168. """
  1169. removed = None
  1170. self.zoomhistory.append((n,s,e,w))
  1171. if len(self.zoomhistory) > 10:
  1172. removed = self.zoomhistory.pop(0)
  1173. if removed:
  1174. Debug.msg(4, "BufferedWindow.ZoomHistory(): hist=%s, removed=%s" %
  1175. (self.zoomhistory, removed))
  1176. else:
  1177. Debug.msg(4, "BufferedWindow.ZoomHistory(): hist=%s" %
  1178. (self.zoomhistory))
  1179. # update toolbar
  1180. if len(self.zoomhistory) > 1:
  1181. enable = True
  1182. else:
  1183. enable = False
  1184. toolbar = self.parent.GetMapToolbar()
  1185. toolbar.Enable('zoomBack', enable)
  1186. return removed
  1187. def ResetZoomHistory(self):
  1188. """!Reset zoom history"""
  1189. self.zoomhistory = list()
  1190. def ZoomToMap(self, layers = None, ignoreNulls = False, render = True):
  1191. """!Set display extents to match selected raster
  1192. or vector map(s).
  1193. @param layers list of layers to be zoom to
  1194. @param ignoreNulls True to ignore null-values (valid only for rasters)
  1195. @param render True to re-render display
  1196. """
  1197. zoomreg = {}
  1198. if not layers:
  1199. layers = self.GetSelectedLayer(multi = True)
  1200. if not layers:
  1201. return
  1202. rast = []
  1203. vect = []
  1204. updated = False
  1205. for l in layers:
  1206. # only raster/vector layers are currently supported
  1207. if l.type == 'raster':
  1208. rast.append(l.GetName())
  1209. elif l.type == 'vector':
  1210. if hasattr(self, "digit") and \
  1211. self.toolbar.GetLayer() == l:
  1212. w, s, b, e, n, t = self.digit.GetDisplay().GetMapBoundingBox()
  1213. self.Map.GetRegion(n = n, s = s, w = w, e = e,
  1214. update = True)
  1215. updated = True
  1216. else:
  1217. vect.append(l.name)
  1218. elif l.type == 'rgb':
  1219. for rname in l.GetName().splitlines():
  1220. rast.append(rname)
  1221. if not updated:
  1222. self.Map.GetRegion(rast = rast,
  1223. vect = vect,
  1224. update = True)
  1225. self.ZoomHistory(self.Map.region['n'], self.Map.region['s'],
  1226. self.Map.region['e'], self.Map.region['w'])
  1227. if render:
  1228. self.UpdateMap()
  1229. self.parent.StatusbarUpdate()
  1230. def ZoomToWind(self):
  1231. """!Set display geometry to match computational region
  1232. settings (set with g.region)
  1233. """
  1234. self.Map.region = self.Map.GetRegion()
  1235. self.ZoomHistory(self.Map.region['n'], self.Map.region['s'],
  1236. self.Map.region['e'], self.Map.region['w'])
  1237. self.UpdateMap()
  1238. self.parent.StatusbarUpdate()
  1239. def ZoomToDefault(self):
  1240. """!Set display geometry to match default region settings
  1241. """
  1242. self.Map.region = self.Map.GetRegion(default = True)
  1243. self.Map.AdjustRegion() # aling region extent to the display
  1244. self.ZoomHistory(self.Map.region['n'], self.Map.region['s'],
  1245. self.Map.region['e'], self.Map.region['w'])
  1246. self.UpdateMap()
  1247. self.parent.StatusbarUpdate()
  1248. def GoTo(self, e, n):
  1249. region = self.Map.GetCurrentRegion()
  1250. region['center_easting'], region['center_northing'] = e, n
  1251. dn = (region['nsres'] * region['rows']) / 2.
  1252. region['n'] = region['center_northing'] + dn
  1253. region['s'] = region['center_northing'] - dn
  1254. de = (region['ewres'] * region['cols']) / 2.
  1255. region['e'] = region['center_easting'] + de
  1256. region['w'] = region['center_easting'] - de
  1257. self.Map.AdjustRegion()
  1258. # add to zoom history
  1259. self.ZoomHistory(region['n'], region['s'],
  1260. region['e'], region['w'])
  1261. self.UpdateMap()
  1262. def DisplayToWind(self):
  1263. """!Set computational region (WIND file) to match display
  1264. extents
  1265. """
  1266. tmpreg = os.getenv("GRASS_REGION")
  1267. if tmpreg:
  1268. del os.environ["GRASS_REGION"]
  1269. # We ONLY want to set extents here. Don't mess with resolution. Leave that
  1270. # for user to set explicitly with g.region
  1271. new = self.Map.AlignResolution()
  1272. RunCommand('g.region',
  1273. parent = self,
  1274. overwrite = True,
  1275. n = new['n'],
  1276. s = new['s'],
  1277. e = new['e'],
  1278. w = new['w'],
  1279. rows = int(new['rows']),
  1280. cols = int(new['cols']))
  1281. if tmpreg:
  1282. os.environ["GRASS_REGION"] = tmpreg
  1283. def ZoomToSaved(self):
  1284. """!Set display geometry to match extents in
  1285. saved region file
  1286. """
  1287. dlg = SavedRegion(parent = self,
  1288. title = _("Zoom to saved region extents"),
  1289. loadsave='load')
  1290. if dlg.ShowModal() == wx.ID_CANCEL or not dlg.wind:
  1291. dlg.Destroy()
  1292. return
  1293. if not grass.find_file(name = dlg.wind, element = 'windows')['name']:
  1294. wx.MessageBox(parent = self,
  1295. message = _("Region <%s> not found. Operation canceled.") % dlg.wind,
  1296. caption = _("Error"), style = wx.ICON_ERROR | wx.OK | wx.CENTRE)
  1297. dlg.Destroy()
  1298. return
  1299. self.Map.GetRegion(regionName = dlg.wind,
  1300. update = True)
  1301. dlg.Destroy()
  1302. self.ZoomHistory(self.Map.region['n'],
  1303. self.Map.region['s'],
  1304. self.Map.region['e'],
  1305. self.Map.region['w'])
  1306. self.UpdateMap()
  1307. def SaveDisplayRegion(self):
  1308. """!Save display extents to named region file.
  1309. """
  1310. dlg = SavedRegion(parent = self,
  1311. title = _("Save display extents to region file"),
  1312. loadsave='save')
  1313. if dlg.ShowModal() == wx.ID_CANCEL or not dlg.wind:
  1314. dlg.Destroy()
  1315. return
  1316. # test to see if it already exists and ask permission to overwrite
  1317. if grass.find_file(name = dlg.wind, element = 'windows')['name']:
  1318. overwrite = wx.MessageBox(parent = self,
  1319. message = _("Region file <%s> already exists. "
  1320. "Do you want to overwrite it?") % (dlg.wind),
  1321. caption = _("Warning"), style = wx.YES_NO | wx.CENTRE)
  1322. if (overwrite == wx.YES):
  1323. self.SaveRegion(dlg.wind)
  1324. else:
  1325. self.SaveRegion(dlg.wind)
  1326. dlg.Destroy()
  1327. def SaveRegion(self, wind):
  1328. """!Save region settings
  1329. @param wind region name
  1330. """
  1331. new = self.Map.GetCurrentRegion()
  1332. tmpreg = os.getenv("GRASS_REGION")
  1333. if tmpreg:
  1334. del os.environ["GRASS_REGION"]
  1335. RunCommand('g.region',
  1336. overwrite = True,
  1337. parent = self,
  1338. flags = 'u',
  1339. n = new['n'],
  1340. s = new['s'],
  1341. e = new['e'],
  1342. w = new['w'],
  1343. rows = int(new['rows']),
  1344. cols = int(new['cols']),
  1345. save = wind)
  1346. if tmpreg:
  1347. os.environ["GRASS_REGION"] = tmpreg
  1348. def Distance(self, beginpt, endpt, screen = True):
  1349. """!Calculete distance
  1350. Ctypes required for LL-locations
  1351. @param beginpt first point
  1352. @param endpt second point
  1353. @param screen True for screen coordinates otherwise EN
  1354. """
  1355. if screen:
  1356. e1, n1 = self.Pixel2Cell(beginpt)
  1357. e2, n2 = self.Pixel2Cell(endpt)
  1358. else:
  1359. e1, n1 = beginpt
  1360. e2, n2 = endpt
  1361. dEast = (e2 - e1)
  1362. dNorth = (n2 - n1)
  1363. if self.parent.Map.projinfo['proj'] == 'll' and haveCtypes:
  1364. dist = gislib.G_distance(e1, n1, e2, n2)
  1365. else:
  1366. dist = math.sqrt(math.pow((dEast), 2) + math.pow((dNorth), 2))
  1367. return (dist, (dEast, dNorth))
  1368. def GetMap(self):
  1369. """!Get render.Map() instance"""
  1370. return self.Map