mapdisp_window.py 62 KB

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