mapdisp_window.py 63 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797
  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. layers = self.GetSelectedLayer(multi = True)
  979. isRaster = False
  980. nVectors = 0
  981. for l in layers:
  982. if l.GetType() == 'raster':
  983. isRaster = True
  984. break
  985. if l.GetType() == 'vector':
  986. nVectors += 1
  987. if isRaster or nVectors > 1:
  988. self.parent.QueryMap(self.mouse['begin'][0],self.mouse['begin'][1])
  989. else:
  990. self.parent.QueryVector(self.mouse['begin'][0], self.mouse['begin'][1])
  991. # clear temp canvas
  992. self.UpdateMap(render = False, renderVector = False)
  993. elif self.mouse["use"] == "queryVector":
  994. # editable mode for vector map layers
  995. self.parent.QueryVector(self.mouse['begin'][0], self.mouse['begin'][1])
  996. # clear temp canvas
  997. self.UpdateMap(render = False, renderVector = False)
  998. elif self.mouse["use"] in ["measure", "profile"]:
  999. # measure or profile
  1000. if self.mouse["use"] == "measure":
  1001. self.parent.MeasureDist(self.mouse['begin'], self.mouse['end'])
  1002. self.polycoords.append(self.Pixel2Cell(self.mouse['end']))
  1003. self.ClearLines(pdc = self.pdcTmp)
  1004. self.DrawLines(pdc = self.pdcTmp)
  1005. elif self.mouse["use"] == "pointer" and \
  1006. not self.parent.IsStandalone() and \
  1007. self.parent.GetLayerManager().gcpmanagement:
  1008. # -> GCP manager
  1009. if self.parent.toolbars['gcpdisp']:
  1010. coord = self.Pixel2Cell(self.mouse['end'])
  1011. if self.parent.MapWindow == self.parent.SrcMapWindow:
  1012. coordtype = 'source'
  1013. else:
  1014. coordtype = 'target'
  1015. self.parent.GetLayerManager().gcpmanagement.SetGCPData(coordtype, coord, self, confirm = True)
  1016. self.UpdateMap(render = False, renderVector = False)
  1017. elif self.mouse["use"] == "pointer" and \
  1018. not self.parent.IsStandalone() and \
  1019. self.parent.GetLayerManager().georectifying:
  1020. # -> georectifying
  1021. coord = self.Pixel2Cell(self.mouse['end'])
  1022. if self.parent.toolbars['georect']:
  1023. coordtype = 'gcpcoord'
  1024. else:
  1025. coordtype = 'mapcoord'
  1026. self.parent.GetLayerManager().georectifying.SetGCPData(coordtype, coord, self)
  1027. self.UpdateMap(render = False, renderVector = False)
  1028. elif self.mouse["use"] == "pointer" and \
  1029. hasattr(self, "digit"):
  1030. self._onLeftUp(event)
  1031. elif (self.mouse['use'] == 'pointer' and
  1032. self.dragid >= 0):
  1033. # end drag of overlay decoration
  1034. if self.dragid < 99 and self.dragid in self.overlays:
  1035. self.overlays[self.dragid]['coords'] = self.pdc.GetIdBounds(self.dragid)
  1036. elif self.dragid > 100 and self.dragid in self.textdict:
  1037. self.textdict[self.dragid]['coords'] = self.pdc.GetIdBounds(self.dragid)
  1038. else:
  1039. pass
  1040. self.dragid = None
  1041. self.currtxtid = None
  1042. def OnButtonDClick(self, event):
  1043. """!Mouse button double click
  1044. """
  1045. Debug.msg (5, "BufferedWindow.OnButtonDClick(): use=%s" % \
  1046. self.mouse["use"])
  1047. if self.mouse["use"] == "measure":
  1048. # measure
  1049. self.ClearLines(pdc=self.pdcTmp)
  1050. self.polycoords = []
  1051. self.mouse['use'] = 'pointer'
  1052. self.mouse['box'] = 'point'
  1053. self.mouse['end'] = [0, 0]
  1054. self.Refresh()
  1055. self.SetCursor(self.parent.cursors["default"])
  1056. elif self.mouse["use"] != "profile" or \
  1057. (self.mouse['use'] != 'pointer' and \
  1058. hasattr(self, "digit")):
  1059. # select overlay decoration options dialog
  1060. clickposition = event.GetPositionTuple()[:]
  1061. idlist = self.pdc.FindObjects(clickposition[0], clickposition[1], self.hitradius)
  1062. if idlist == []:
  1063. return
  1064. self.dragid = idlist[0]
  1065. # self.ovlcoords[self.dragid] = self.pdc.GetIdBounds(self.dragid)
  1066. if self.dragid > 100:
  1067. self.currtxtid = self.dragid
  1068. self.parent.OnAddText(None)
  1069. elif self.dragid == 0:
  1070. self.parent.OnAddBarscale(None)
  1071. elif self.dragid == 1:
  1072. self.parent.OnAddLegend(None)
  1073. def OnRightDown(self, event):
  1074. """!Right mouse button pressed
  1075. """
  1076. Debug.msg (5, "BufferedWindow.OnRightDown(): use=%s" % \
  1077. self.mouse["use"])
  1078. if hasattr(self, "digit"):
  1079. self._onRightDown(event)
  1080. event.Skip()
  1081. def OnRightUp(self, event):
  1082. """!Right mouse button released
  1083. """
  1084. Debug.msg (5, "BufferedWindow.OnRightUp(): use=%s" % \
  1085. self.mouse["use"])
  1086. if hasattr(self, "digit"):
  1087. self._onRightUp(event)
  1088. self.redrawAll = True
  1089. self.Refresh()
  1090. event.Skip()
  1091. def OnMiddleDown(self, event):
  1092. """!Middle mouse button pressed
  1093. """
  1094. if not event:
  1095. return
  1096. self.mouse['begin'] = event.GetPositionTuple()[:]
  1097. def OnMiddleUp(self, event):
  1098. """!Middle mouse button released
  1099. """
  1100. self.mouse['end'] = event.GetPositionTuple()[:]
  1101. # set region in zoom or pan
  1102. begin = self.mouse['begin']
  1103. end = self.mouse['end']
  1104. self.Zoom(begin, end, 0) # no zoom
  1105. # redraw map
  1106. self.UpdateMap(render = True)
  1107. # update statusbar
  1108. self.parent.StatusbarUpdate()
  1109. def OnMouseEnter(self, event):
  1110. """!Mouse entered window and no mouse buttons were pressed
  1111. """
  1112. if not self.parent.IsStandalone() and \
  1113. self.parent.GetLayerManager().gcpmanagement:
  1114. if self.parent.toolbars['gcpdisp']:
  1115. if not self.parent.MapWindow == self:
  1116. self.parent.MapWindow = self
  1117. self.parent.Map = self.Map
  1118. self.parent.UpdateActive(self)
  1119. # needed for wingrass
  1120. self.SetFocus()
  1121. else:
  1122. event.Skip()
  1123. def OnMouseMoving(self, event):
  1124. """!Motion event and no mouse buttons were pressed
  1125. """
  1126. if self.mouse["use"] == "pointer" and \
  1127. hasattr(self, "digit"):
  1128. self._onMouseMoving(event)
  1129. event.Skip()
  1130. def ClearLines(self, pdc = None):
  1131. """!Clears temporary drawn lines from PseudoDC
  1132. """
  1133. if not pdc:
  1134. pdc = self.pdcTmp
  1135. try:
  1136. pdc.ClearId(self.lineid)
  1137. pdc.RemoveId(self.lineid)
  1138. except:
  1139. pass
  1140. try:
  1141. pdc.ClearId(self.plineid)
  1142. pdc.RemoveId(self.plineid)
  1143. except:
  1144. pass
  1145. Debug.msg(4, "BufferedWindow.ClearLines(): lineid=%s, plineid=%s" %
  1146. (self.lineid, self.plineid))
  1147. return True
  1148. def Pixel2Cell(self, (x, y)):
  1149. """!Convert image coordinates to real word coordinates
  1150. @param x, y image coordinates
  1151. @return easting, northing
  1152. @return None on error
  1153. """
  1154. try:
  1155. x = int(x)
  1156. y = int(y)
  1157. except:
  1158. return None
  1159. if self.Map.region["ewres"] > self.Map.region["nsres"]:
  1160. res = self.Map.region["ewres"]
  1161. else:
  1162. res = self.Map.region["nsres"]
  1163. w = self.Map.region["center_easting"] - (self.Map.width / 2) * res
  1164. n = self.Map.region["center_northing"] + (self.Map.height / 2) * res
  1165. east = w + x * res
  1166. north = n - y * res
  1167. return (east, north)
  1168. def Cell2Pixel(self, (east, north)):
  1169. """!Convert real word coordinates to image coordinates
  1170. """
  1171. try:
  1172. east = float(east)
  1173. north = float(north)
  1174. except:
  1175. return None
  1176. if self.Map.region["ewres"] > self.Map.region["nsres"]:
  1177. res = self.Map.region["ewres"]
  1178. else:
  1179. res = self.Map.region["nsres"]
  1180. w = self.Map.region["center_easting"] - (self.Map.width / 2) * res
  1181. n = self.Map.region["center_northing"] + (self.Map.height / 2) * res
  1182. x = (east - w) / res
  1183. y = (n - north) / res
  1184. return (x, y)
  1185. def Zoom(self, begin, end, zoomtype):
  1186. """!Calculates new region while (un)zoom/pan-ing
  1187. """
  1188. x1, y1 = begin
  1189. x2, y2 = end
  1190. newreg = {}
  1191. # threshold - too small squares do not make sense
  1192. # can only zoom to windows of > 5x5 screen pixels
  1193. if abs(x2-x1) > 5 and abs(y2-y1) > 5 and zoomtype != 0:
  1194. if x1 > x2:
  1195. x1, x2 = x2, x1
  1196. if y1 > y2:
  1197. y1, y2 = y2, y1
  1198. # zoom in
  1199. if zoomtype > 0:
  1200. newreg['w'], newreg['n'] = self.Pixel2Cell((x1, y1))
  1201. newreg['e'], newreg['s'] = self.Pixel2Cell((x2, y2))
  1202. # zoom out
  1203. elif zoomtype < 0:
  1204. newreg['w'], newreg['n'] = self.Pixel2Cell((-x1 * 2, -y1 * 2))
  1205. newreg['e'], newreg['s'] = self.Pixel2Cell((self.Map.width + 2 * \
  1206. (self.Map.width - x2),
  1207. self.Map.height + 2 * \
  1208. (self.Map.height - y2)))
  1209. # pan
  1210. elif zoomtype == 0:
  1211. dx = x1 - x2
  1212. dy = y1 - y2
  1213. if dx == 0 and dy == 0:
  1214. dx = x1 - self.Map.width / 2
  1215. dy = y1 - self.Map.height / 2
  1216. newreg['w'], newreg['n'] = self.Pixel2Cell((dx, dy))
  1217. newreg['e'], newreg['s'] = self.Pixel2Cell((self.Map.width + dx,
  1218. self.Map.height + dy))
  1219. # if new region has been calculated, set the values
  1220. if newreg != {}:
  1221. # LL locations
  1222. if self.parent.Map.projinfo['proj'] == 'll':
  1223. if newreg['n'] > 90.0:
  1224. newreg['n'] = 90.0
  1225. if newreg['s'] < -90.0:
  1226. newreg['s'] = -90.0
  1227. ce = newreg['w'] + (newreg['e'] - newreg['w']) / 2
  1228. cn = newreg['s'] + (newreg['n'] - newreg['s']) / 2
  1229. # calculate new center point and display resolution
  1230. self.Map.region['center_easting'] = ce
  1231. self.Map.region['center_northing'] = cn
  1232. self.Map.region["ewres"] = (newreg['e'] - newreg['w']) / self.Map.width
  1233. self.Map.region["nsres"] = (newreg['n'] - newreg['s']) / self.Map.height
  1234. self.Map.AlignExtentFromDisplay()
  1235. if hasattr(self, "digit") and \
  1236. hasattr(self, "moveInfo"):
  1237. self._zoom(event)
  1238. self.ZoomHistory(self.Map.region['n'], self.Map.region['s'],
  1239. self.Map.region['e'], self.Map.region['w'])
  1240. if self.redrawAll is False:
  1241. self.redrawAll = True
  1242. def ZoomBack(self):
  1243. """!Zoom to previous extents in zoomhistory list
  1244. """
  1245. zoom = list()
  1246. if len(self.zoomhistory) > 1:
  1247. self.zoomhistory.pop()
  1248. zoom = self.zoomhistory[-1]
  1249. # disable tool if stack is empty
  1250. if len(self.zoomhistory) < 2: # disable tool
  1251. if self.parent.GetName() == 'MapWindow':
  1252. toolbar = self.parent.toolbars['map']
  1253. elif self.parent.GetName() == 'GRMapWindow':
  1254. toolbar = self.parent.toolbars['georect']
  1255. elif self.parent.GetName() == 'GCPMapWindow':
  1256. toolbar = self.parent.toolbars['gcpdisp']
  1257. toolbar.Enable('zoomback', enable = False)
  1258. # zoom to selected region
  1259. self.Map.GetRegion(n = zoom[0], s = zoom[1],
  1260. e = zoom[2], w = zoom[3],
  1261. update = True)
  1262. # update map
  1263. self.UpdateMap()
  1264. # update statusbar
  1265. self.parent.StatusbarUpdate()
  1266. def ZoomHistory(self, n, s, e, w):
  1267. """!Manages a list of last 10 zoom extents
  1268. @param n,s,e,w north, south, east, west
  1269. @return removed history item if exists (or None)
  1270. """
  1271. removed = None
  1272. self.zoomhistory.append((n,s,e,w))
  1273. if len(self.zoomhistory) > 10:
  1274. removed = self.zoomhistory.pop(0)
  1275. if removed:
  1276. Debug.msg(4, "BufferedWindow.ZoomHistory(): hist=%s, removed=%s" %
  1277. (self.zoomhistory, removed))
  1278. else:
  1279. Debug.msg(4, "BufferedWindow.ZoomHistory(): hist=%s" %
  1280. (self.zoomhistory))
  1281. # update toolbar
  1282. if len(self.zoomhistory) > 1:
  1283. enable = True
  1284. else:
  1285. enable = False
  1286. if self.parent.GetName() == 'MapWindow':
  1287. toolbar = self.parent.toolbars['map']
  1288. elif self.parent.GetName() == 'GRMapWindow':
  1289. toolbar = self.parent.toolbars['georect']
  1290. elif self.parent.GetName() == 'GCPMapWindow':
  1291. toolbar = self.parent.toolbars['gcpdisp']
  1292. toolbar.Enable('zoomback', enable)
  1293. return removed
  1294. def ResetZoomHistory(self):
  1295. """!Reset zoom history"""
  1296. self.zoomhistory = list()
  1297. def ZoomToMap(self, layers = None, ignoreNulls = False, render = True):
  1298. """!Set display extents to match selected raster
  1299. or vector map(s).
  1300. @param layers list of layers to be zoom to
  1301. @param ignoreNulls True to ignore null-values (valid only for rasters)
  1302. @param render True to re-render display
  1303. """
  1304. zoomreg = {}
  1305. if not layers:
  1306. layers = self.GetSelectedLayer(multi = True)
  1307. if not layers:
  1308. return
  1309. rast = []
  1310. vect = []
  1311. updated = False
  1312. for l in layers:
  1313. # only raster/vector layers are currently supported
  1314. if l.type == 'raster':
  1315. rast.append(l.GetName())
  1316. elif l.type == 'vector':
  1317. if hasattr(self, "digit") and \
  1318. self.toolbar.GetLayer() == l:
  1319. w, s, b, e, n, t = self.display.GetMapBoundingBox()
  1320. self.Map.GetRegion(n = n, s = s, w = w, e = e,
  1321. update = True)
  1322. updated = True
  1323. else:
  1324. vect.append(l.name)
  1325. elif l.type == 'rgb':
  1326. for rname in l.GetName().splitlines():
  1327. rast.append(rname)
  1328. if not updated:
  1329. self.Map.GetRegion(rast = rast,
  1330. vect = vect,
  1331. update = True)
  1332. self.ZoomHistory(self.Map.region['n'], self.Map.region['s'],
  1333. self.Map.region['e'], self.Map.region['w'])
  1334. if render:
  1335. self.UpdateMap()
  1336. self.parent.StatusbarUpdate()
  1337. def ZoomToWind(self):
  1338. """!Set display geometry to match computational region
  1339. settings (set with g.region)
  1340. """
  1341. self.Map.region = self.Map.GetRegion()
  1342. self.ZoomHistory(self.Map.region['n'], self.Map.region['s'],
  1343. self.Map.region['e'], self.Map.region['w'])
  1344. self.UpdateMap()
  1345. self.parent.StatusbarUpdate()
  1346. def ZoomToDefault(self):
  1347. """!Set display geometry to match default region settings
  1348. """
  1349. self.Map.region = self.Map.GetRegion(default = True)
  1350. self.Map.AdjustRegion() # aling region extent to the display
  1351. self.ZoomHistory(self.Map.region['n'], self.Map.region['s'],
  1352. self.Map.region['e'], self.Map.region['w'])
  1353. self.UpdateMap()
  1354. self.parent.StatusbarUpdate()
  1355. def DisplayToWind(self):
  1356. """!Set computational region (WIND file) to match display
  1357. extents
  1358. """
  1359. tmpreg = os.getenv("GRASS_REGION")
  1360. if tmpreg:
  1361. del os.environ["GRASS_REGION"]
  1362. # We ONLY want to set extents here. Don't mess with resolution. Leave that
  1363. # for user to set explicitly with g.region
  1364. new = self.Map.AlignResolution()
  1365. gcmd.RunCommand('g.region',
  1366. parent = self,
  1367. overwrite = True,
  1368. n = new['n'],
  1369. s = new['s'],
  1370. e = new['e'],
  1371. w = new['w'],
  1372. rows = int(new['rows']),
  1373. cols = int(new['cols']))
  1374. if tmpreg:
  1375. os.environ["GRASS_REGION"] = tmpreg
  1376. def ZoomToSaved(self):
  1377. """!Set display geometry to match extents in
  1378. saved region file
  1379. """
  1380. dlg = gdialogs.SavedRegion(parent = self,
  1381. title = _("Zoom to saved region extents"),
  1382. loadsave='load')
  1383. if dlg.ShowModal() == wx.ID_CANCEL or not dlg.wind:
  1384. dlg.Destroy()
  1385. return
  1386. if not grass.find_file(name = dlg.wind, element = 'windows')['name']:
  1387. wx.MessageBox(parent = self,
  1388. message = _("Region <%s> not found. Operation canceled.") % dlg.wind,
  1389. caption = _("Error"), style = wx.ICON_ERROR | wx.OK | wx.CENTRE)
  1390. dlg.Destroy()
  1391. return
  1392. self.Map.GetRegion(regionName = dlg.wind,
  1393. update = True)
  1394. dlg.Destroy()
  1395. self.ZoomHistory(self.Map.region['n'],
  1396. self.Map.region['s'],
  1397. self.Map.region['e'],
  1398. self.Map.region['w'])
  1399. self.UpdateMap()
  1400. def SaveDisplayRegion(self):
  1401. """!Save display extents to named region file.
  1402. """
  1403. dlg = gdialogs.SavedRegion(parent = self,
  1404. title = _("Save display extents to region file"),
  1405. loadsave='save')
  1406. if dlg.ShowModal() == wx.ID_CANCEL or not dlg.wind:
  1407. dlg.Destroy()
  1408. return
  1409. # test to see if it already exists and ask permission to overwrite
  1410. if grass.find_file(name = dlg.wind, element = 'windows')['name']:
  1411. overwrite = wx.MessageBox(parent = self,
  1412. message = _("Region file <%s> already exists. "
  1413. "Do you want to overwrite it?") % (dlg.wind),
  1414. caption = _("Warning"), style = wx.YES_NO | wx.CENTRE)
  1415. if (overwrite == wx.YES):
  1416. self.SaveRegion(dlg.wind)
  1417. else:
  1418. self.SaveRegion(dlg.wind)
  1419. dlg.Destroy()
  1420. def SaveRegion(self, wind):
  1421. """!Save region settings
  1422. @param wind region name
  1423. """
  1424. new = self.Map.GetCurrentRegion()
  1425. tmpreg = os.getenv("GRASS_REGION")
  1426. if tmpreg:
  1427. del os.environ["GRASS_REGION"]
  1428. gcmd.RunCommand('g.region',
  1429. overwrite = True,
  1430. parent = self,
  1431. flags = 'u',
  1432. n = new['n'],
  1433. s = new['s'],
  1434. e = new['e'],
  1435. w = new['w'],
  1436. rows = int(new['rows']),
  1437. cols = int(new['cols']),
  1438. save = wind)
  1439. if tmpreg:
  1440. os.environ["GRASS_REGION"] = tmpreg
  1441. def Distance(self, beginpt, endpt, screen = True):
  1442. """!Calculete distance
  1443. Ctypes required for LL-locations
  1444. @param beginpt first point
  1445. @param endpt second point
  1446. @param screen True for screen coordinates otherwise EN
  1447. """
  1448. if screen:
  1449. e1, n1 = self.Pixel2Cell(beginpt)
  1450. e2, n2 = self.Pixel2Cell(endpt)
  1451. else:
  1452. e1, n1 = beginpt
  1453. e2, n2 = endpt
  1454. dEast = (e2 - e1)
  1455. dNorth = (n2 - n1)
  1456. if self.parent.Map.projinfo['proj'] == 'll' and haveCtypes:
  1457. dist = gislib.G_distance(e1, n1, e2, n2)
  1458. else:
  1459. dist = math.sqrt(math.pow((dEast), 2) + math.pow((dNorth), 2))
  1460. return (dist, (dEast, dNorth))