wxdisplay.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056
  1. """!
  2. @package vdigit.wxdisplay
  3. @brief wxGUI vector digitizer (display driver)
  4. Code based on wxVdigit C++ component from GRASS 6.4.0
  5. (gui/wxpython/vdigit). Converted to Python in 2010/12-2011/01.
  6. List of classes:
  7. - wxdisplay::DisplayDriver
  8. (C) 2007-2011 by the GRASS Development Team
  9. This program is free software under the GNU General Public License
  10. (>=v2). Read the file COPYING that comes with GRASS for details.
  11. @author Martin Landa <landa.martin gmail.com>
  12. """
  13. import locale
  14. import wx
  15. from core.debug import Debug
  16. from core.settings import UserSettings
  17. try:
  18. from grass.lib.gis import *
  19. from grass.lib.vector import *
  20. from grass.lib.vedit import *
  21. except ImportError:
  22. pass
  23. log = None
  24. progress = None
  25. last_error = ''
  26. def print_error(msg, type):
  27. """!Redirect stderr"""
  28. global log
  29. if log:
  30. log.write(msg + os.linesep)
  31. else:
  32. print msg
  33. global last_error
  34. last_error += ' ' + msg
  35. return 0
  36. def print_progress(value):
  37. """!Redirect progress info"""
  38. global progress
  39. if progress:
  40. progress.SetValue(value)
  41. else:
  42. pass # discard progress info
  43. return 0
  44. def GetLastError():
  45. global last_error
  46. ret = last_error
  47. if ret[-1] != '.':
  48. ret += '.'
  49. last_error = '' # reset
  50. return ret
  51. try:
  52. errtype = CFUNCTYPE(UNCHECKED(c_int), String, c_int)
  53. errfunc = errtype(print_error)
  54. pertype = CFUNCTYPE(UNCHECKED(c_int), c_int)
  55. perfunc = pertype(print_progress)
  56. except NameError:
  57. pass
  58. class DisplayDriver:
  59. def __init__(self, device, deviceTmp, mapObj, window, glog, gprogress):
  60. """!Display driver used by vector digitizer
  61. @param device wx.PseudoDC device where to draw vector objects
  62. @param deviceTmp wx.PseudoDC device where to draw temporary vector objects
  63. @param mapOng Map Object (render.Map)
  64. @param windiow parent window for dialogs
  65. @param glog logging device (None to discard messages)
  66. @param gprogress progress bar device (None to discard message)
  67. """
  68. global errfunc, perfunc, log, progress
  69. log = glog
  70. progress = gprogress
  71. G_gisinit('wxvdigit')
  72. locale.setlocale(locale.LC_NUMERIC, 'C')
  73. G_set_error_routine(errfunc)
  74. G_set_percent_routine(perfunc)
  75. # G_set_fatal_error(FATAL_RETURN)
  76. self.mapInfo = None # open vector map (Map_Info structure)
  77. self.poMapInfo = None # pointer to self.mapInfo
  78. self.is3D = False # is open vector map 3D
  79. self.dc = device # PseudoDC devices
  80. self.dcTmp = deviceTmp
  81. self.mapObj = mapObj
  82. self.region = mapObj.GetCurrentRegion()
  83. self.window = window
  84. self.log = log # log device
  85. self.firstNode = True # track PseudoDC Id of selected features
  86. self.lastNodeId = -1
  87. # GRASS lib
  88. self.poPoints = Vect_new_line_struct()
  89. self.poCats = Vect_new_cats_struct()
  90. # selected objects
  91. self.selected = {
  92. 'field' : -1, # field number
  93. 'cats' : list(), # list of cats
  94. 'ids' : list(), # list of ids
  95. 'idsDupl' : list(), # list of duplicated features
  96. }
  97. # digitizer settings
  98. self.settings = {
  99. 'highlight' : None,
  100. 'highlightDupl' : { 'enabled' : False,
  101. 'color' : None },
  102. 'point' : { 'enabled' : False,
  103. 'color' : None },
  104. 'line' : { 'enabled' : False,
  105. 'color' : None },
  106. 'boundaryNo' : { 'enabled' : False,
  107. 'color' : None },
  108. 'boundaryOne' : { 'enabled' : False,
  109. 'color' : None },
  110. 'boundaryTwo' : { 'enabled' : False,
  111. 'color' : None },
  112. 'centroidIn' : { 'enabled' : False,
  113. 'color' : None },
  114. 'centroidOut' : { 'enabled' : False,
  115. 'color' : None },
  116. 'centroidDup' : { 'enabled' : False,
  117. 'color' : None },
  118. 'nodeOne' : { 'enabled' : False,
  119. 'color' : None },
  120. 'nodeTwo' : { 'enabled' : False,
  121. 'color' : None },
  122. 'vertex' : { 'enabled' : False,
  123. 'color' : None },
  124. 'area' : { 'enabled' : False,
  125. 'color' : None },
  126. 'direction' : { 'enabled' : False,
  127. 'color' : None },
  128. 'lineWidth' : -1, # screen units
  129. }
  130. # topology
  131. self._resetTopology()
  132. self._drawSelected = False
  133. self._drawSegments = False
  134. self.UpdateSettings()
  135. def __del__(self):
  136. """!Close currently open vector map"""
  137. G_unset_error_routine()
  138. G_unset_percent_routine()
  139. if self.poMapInfo:
  140. self.CloseMap()
  141. Vect_destroy_line_struct(self.poPoints)
  142. Vect_destroy_cats_struct(self.poCats)
  143. def _resetTopology(self):
  144. """!Reset topology dict
  145. """
  146. self.topology = {
  147. 'highlight' : 0,
  148. 'point' : 0,
  149. 'line' : 0,
  150. 'boundaryNo' : 0,
  151. 'boundaryOne' : 0,
  152. 'boundaryTwo' : 0,
  153. 'centroidIn' : 0,
  154. 'centroidOut' : 0,
  155. 'centroidDup' : 0,
  156. 'nodeOne' : 0,
  157. 'nodeTwo' : 0,
  158. 'vertex' : 0,
  159. }
  160. def _cell2Pixel(self, east, north, elev):
  161. """!Conversion from geographic coordinates (east, north)
  162. to screen (x, y)
  163. @todo 3D stuff...
  164. @param east, north, elev geographical coordinates
  165. @return x, y screen coordinates (integer)
  166. """
  167. map_res = max(self.region['ewres'], self.region['nsres'])
  168. w = self.region['center_easting'] - (self.mapObj.width / 2) * map_res
  169. n = self.region['center_northing'] + (self.mapObj.height / 2) * map_res
  170. return int((east - w) / map_res), int((n - north) / map_res)
  171. def _drawCross(self, pdc, point, size = 5):
  172. """!Draw cross symbol of given size to device content
  173. Used for points, nodes, vertices
  174. @param[in,out] PseudoDC where to draw
  175. @param point coordinates of center
  176. @param size size of the cross symbol
  177. @return 0 on success
  178. @return -1 on failure
  179. """
  180. if not pdc or not point:
  181. return -1
  182. pdc.DrawLine(point.x - size, point.y, point.x + size, point.y)
  183. pdc.DrawLine(point.x, point.y - size, point.x, point.y + size)
  184. return 0
  185. def _drawObject(self, robj):
  186. """!Draw given object to the device
  187. The object is defined as robject() from vedit.h.
  188. @param robj object to be rendered
  189. @return 1 on success
  190. @return -1 on failure (vector feature marked as dead, etc.)
  191. """
  192. if not self.dc or not self.dcTmp:
  193. return -1
  194. Debug.msg(3, "_drawObject(): line=%d type=%d npoints=%d", robj.fid, robj.type, robj.npoints)
  195. brush = None
  196. if self._isSelected(robj.fid):
  197. pdc = self.dcTmp
  198. if robj.type == TYPE_AREA:
  199. return 1
  200. else:
  201. if self.settings['highlightDupl']['enabled'] and self._isDuplicated(robj.fid):
  202. pen = wx.Pen(self.settings['highlightDupl']['color'], self.settings['lineWidth'], wx.SOLID)
  203. else:
  204. pen = wx.Pen(self.settings['highlight'], self.settings['lineWidth'], wx.SOLID)
  205. dcId = 1
  206. self.topology['highlight'] += 1
  207. if not self._drawSelected:
  208. return
  209. else:
  210. pdc = self.dc
  211. pen, brush = self._definePen(robj.type)
  212. dcId = 0
  213. pdc.SetPen(pen)
  214. if brush:
  215. pdc.SetBrush(brush)
  216. if robj.type & (TYPE_POINT | TYPE_CENTROIDIN | TYPE_CENTROIDOUT | TYPE_CENTROIDDUP |
  217. TYPE_NODEONE | TYPE_NODETWO | TYPE_VERTEX): # -> point
  218. if dcId > 0:
  219. if robj.type == TYPE_VERTEX:
  220. dcId = 3 # first vertex
  221. elif robj.type & (TYPE_NODEONE | TYPE_NODETWO):
  222. if self.firstNode:
  223. dcId = 1
  224. self.firstNode = False
  225. else:
  226. dcId = self.lastNodeId
  227. for i in range(robj.npoints):
  228. p = robj.point[i]
  229. if dcId > 0:
  230. pdc.SetId(dcId)
  231. dcId += 2
  232. self._drawCross(pdc, p)
  233. else:
  234. if dcId > 0 and self._drawSegments:
  235. self.fisrtNode = True
  236. self.lastNodeId = robj.npoints * 2 - 1
  237. dcId = 2 # first segment
  238. i = 0
  239. while i < robj.npoints - 1:
  240. point_beg = wx.Point(robj.point[i].x, robj.point[i].y)
  241. point_end = wx.Point(robj.point[i+1].x, robj.point[i+1].y)
  242. pdc.SetId(dcId) # set unique id & set bbox for each segment
  243. pdc.SetPen(pen)
  244. pdc.SetIdBounds(dcId - 1, wx.Rect(point_beg.x, point_beg.y, 0, 0))
  245. pdc.SetIdBounds(dcId, wx.RectPP(point_beg, point_end))
  246. pdc.DrawLine(point_beg.x, point_beg.y,
  247. point_end.x, point_end.y)
  248. i += 1
  249. dcId += 2
  250. pdc.SetIdBounds(dcId - 1, wx.Rect(robj.point[robj.npoints - 1].x,
  251. robj.point[robj.npoints - 1].y,
  252. 0, 0))
  253. else:
  254. points = list()
  255. for i in range(robj.npoints):
  256. p = robj.point[i]
  257. points.append(wx.Point(p.x, p.y))
  258. if robj.type == TYPE_AREA:
  259. pdc.DrawPolygon(points)
  260. else:
  261. pdc.DrawLines(points)
  262. def _definePen(self, rtype):
  263. """!Define pen/brush based on rendered object)
  264. Updates also self.topology dict
  265. @return pen, brush
  266. """
  267. if rtype == TYPE_POINT:
  268. key = 'point'
  269. elif rtype == TYPE_LINE:
  270. key = 'line'
  271. elif rtype == TYPE_BOUNDARYNO:
  272. key = 'boundaryNo'
  273. elif rtype == TYPE_BOUNDARYTWO:
  274. key = 'boundaryTwo'
  275. elif rtype == TYPE_BOUNDARYONE:
  276. key = 'boundaryOne'
  277. elif rtype == TYPE_CENTROIDIN:
  278. key = 'centroidIn'
  279. elif rtype == TYPE_CENTROIDOUT:
  280. key = 'centroidOut'
  281. elif rtype == TYPE_CENTROIDDUP:
  282. key = 'centroidDup'
  283. elif rtype == TYPE_NODEONE:
  284. key = 'nodeOne'
  285. elif rtype == TYPE_NODETWO:
  286. key = 'nodeTwo'
  287. elif rtype == TYPE_VERTEX:
  288. key = 'vertex'
  289. elif rtype == TYPE_AREA:
  290. key = 'area'
  291. elif rtype == TYPE_ISLE:
  292. key = 'isle'
  293. elif rtype == TYPE_DIRECTION:
  294. key = 'direction'
  295. if key not in ('direction', 'area', 'isle'):
  296. self.topology[key] += 1
  297. if key in ('area', 'isle'):
  298. pen = wx.TRANSPARENT_PEN
  299. if key == 'area':
  300. brush = wx.Brush(self.settings[key]['color'], wx.SOLID)
  301. else:
  302. brush = wx.TRANSPARENT_BRUSH
  303. else:
  304. pen = wx.Pen(self.settings[key]['color'], self.settings['lineWidth'], wx.SOLID)
  305. brush = None
  306. return pen, brush
  307. def _getDrawFlag(self):
  308. """!Get draw flag from the settings
  309. See vedit.h for list of draw flags.
  310. @return draw flag (int)
  311. """
  312. ret = 0
  313. if self.settings['point']['enabled']:
  314. ret |= DRAW_POINT
  315. if self.settings['line']['enabled']:
  316. ret |= DRAW_LINE
  317. if self.settings['boundaryNo']['enabled']:
  318. ret |= DRAW_BOUNDARYNO
  319. if self.settings['boundaryTwo']['enabled']:
  320. ret |= DRAW_BOUNDARYTWO
  321. if self.settings['boundaryOne']['enabled']:
  322. ret |= DRAW_BOUNDARYONE
  323. if self.settings['centroidIn']['enabled']:
  324. ret |= DRAW_CENTROIDIN
  325. if self.settings['centroidOut']['enabled']:
  326. ret |= DRAW_CENTROIDOUT
  327. if self.settings['centroidDup']['enabled']:
  328. ret |= DRAW_CENTROIDDUP
  329. if self.settings['nodeOne']['enabled']:
  330. ret |= DRAW_NODEONE
  331. if self.settings['nodeTwo']['enabled']:
  332. ret |= DRAW_NODETWO
  333. if self.settings['vertex']['enabled']:
  334. ret |= DRAW_VERTEX
  335. if self.settings['area']['enabled']:
  336. ret |= DRAW_AREA
  337. if self.settings['direction']['enabled']:
  338. ret |= DRAW_DIRECTION
  339. return ret
  340. def _isSelected(self, line, force = False):
  341. """!Check if vector object selected?
  342. @param line feature id
  343. @return True if vector object is selected
  344. @return False if vector object is not selected
  345. """
  346. if line in self.selected['ids']:
  347. return True
  348. return False
  349. def _isDuplicated(self, line):
  350. """!Check for already marked duplicates
  351. @param line feature id
  352. @return True line already marked as duplicated
  353. @return False not duplicated
  354. """
  355. return line in self.selected['idsDupl']
  356. def _getRegionBox(self):
  357. """!Get bound_box() from current region
  358. @return bound_box
  359. """
  360. box = bound_box()
  361. box.N = self.region['n']
  362. box.S = self.region['s']
  363. box.E = self.region['e']
  364. box.W = self.region['w']
  365. box.T = PORT_DOUBLE_MAX
  366. box.B = -PORT_DOUBLE_MAX
  367. return box
  368. def DrawMap(self, force = False):
  369. """!Draw content of the vector map to the device
  370. @param force force drawing
  371. @return number of drawn features
  372. @return -1 on error
  373. """
  374. Debug.msg(1, "DisplayDriver.DrawMap(): force=%d", force)
  375. if not self.poMapInfo or not self.dc or not self.dcTmp:
  376. return -1
  377. rlist = Vedit_render_map(self.poMapInfo, byref(self._getRegionBox()), self._getDrawFlag(),
  378. self.region['center_easting'], self.region['center_northing'],
  379. self.mapObj.width, self.mapObj.height,
  380. max(self.region['nsres'], self.region['ewres'])).contents
  381. self._resetTopology()
  382. self.dc.BeginDrawing()
  383. self.dcTmp.BeginDrawing()
  384. # draw objects
  385. for i in range(rlist.nitems):
  386. robj = rlist.item[i].contents
  387. self._drawObject(robj)
  388. self.dc.EndDrawing()
  389. self.dcTmp.EndDrawing()
  390. # reset list of selected features by cat
  391. # list of ids - see IsSelected()
  392. self.selected['field'] = -1
  393. self.selected['cats'] = list()
  394. def _getSelectType(self):
  395. """!Get type(s) to be selected
  396. Used by SelectLinesByBox() and SelectLineByPoint()
  397. """
  398. ftype = 0
  399. for feature in (('point', GV_POINT),
  400. ('line', GV_LINE),
  401. ('centroid', GV_CENTROID),
  402. ('boundary', GV_BOUNDARY)):
  403. if UserSettings.Get(group = 'vdigit', key = 'selectType',
  404. subkey = [feature[0], 'enabled']):
  405. ftype |= feature[1]
  406. return ftype
  407. def _validLine(self, line):
  408. """!Check if feature id is valid
  409. @param line feature id
  410. @return True valid feature id
  411. @return False invalid
  412. """
  413. if line > 0 and line <= Vect_get_num_lines(self.poMapInfo):
  414. return True
  415. return False
  416. def SelectLinesByBox(self, bbox, drawSeg = False, poMapInfo = None):
  417. """!Select vector objects by given bounding box
  418. If line id is already in the list of selected lines, then it will
  419. be excluded from this list.
  420. @param bbox bounding box definition
  421. @param drawSeg True to draw segments of line
  422. @param poMapInfo use external Map_info, None for self.poMapInfo
  423. @return number of selected features
  424. @return None on error
  425. """
  426. thisMapInfo = poMapInfo is None
  427. if not poMapInfo:
  428. poMapInfo = self.poMapInfo
  429. if not poMapInfo:
  430. return None
  431. if thisMapInfo:
  432. self._drawSegments = drawSeg
  433. self._drawSelected = True
  434. # select by ids
  435. self.selected['cats'] = list()
  436. poList = Vect_new_list()
  437. x1, y1 = bbox[0]
  438. x2, y2 = bbox[1]
  439. poBbox = Vect_new_line_struct()
  440. Vect_append_point(poBbox, x1, y1, 0.0)
  441. Vect_append_point(poBbox, x2, y1, 0.0)
  442. Vect_append_point(poBbox, x2, y2, 0.0)
  443. Vect_append_point(poBbox, x1, y2, 0.0)
  444. Vect_append_point(poBbox, x1, y1, 0.0)
  445. Vect_select_lines_by_polygon(poMapInfo, poBbox,
  446. 0, None, # isles
  447. self._getSelectType(), poList)
  448. flist = poList.contents
  449. nlines = flist.n_values
  450. Debug.msg(1, "DisplayDriver.SelectLinesByBox() num = %d", nlines)
  451. for i in range(nlines):
  452. line = flist.value[i]
  453. if UserSettings.Get(group = 'vdigit', key = 'selectInside',
  454. subkey = 'enabled'):
  455. inside = True
  456. if not self._validLine(line):
  457. return None
  458. Vect_read_line(poMapInfo, self.poPoints, None, line)
  459. points = self.poPoints.contents
  460. for p in range(points.n_points):
  461. if not Vect_point_in_poly(points.x[p], points.y[p],
  462. poBbox):
  463. inside = False
  464. break
  465. if not inside:
  466. continue # skip lines just overlapping bbox
  467. if not self._isSelected(line):
  468. self.selected['ids'].append(line)
  469. else:
  470. self.selected['ids'].remove(line)
  471. Vect_destroy_line_struct(poBbox)
  472. Vect_destroy_list(poList)
  473. return nlines
  474. def SelectLineByPoint(self, point, poMapInfo = None):
  475. """!Select vector feature by given point in given
  476. threshold
  477. Only one vector object can be selected. Bounding boxes of
  478. all segments are stores.
  479. @param point points coordinates (x, y)
  480. @param poMapInfo use external Map_info, None for self.poMapInfo
  481. @return dict {'line' : feature id, 'point' : point on line}
  482. """
  483. thisMapInfo = poMapInfo is None
  484. if not poMapInfo:
  485. poMapInfo = self.poMapInfo
  486. if not poMapInfo:
  487. return { 'line' : -1, 'point': None }
  488. if thisMapInfo:
  489. self._drawSelected = True
  490. # select by ids
  491. self.selected['cats'] = list()
  492. poFound = Vect_new_list()
  493. lineNearest = Vect_find_line_list(poMapInfo, point[0], point[1], 0,
  494. self._getSelectType(), self.GetThreshold(), self.is3D,
  495. None, poFound)
  496. Debug.msg(1, "DisplayDriver.SelectLineByPoint() found = %d", lineNearest)
  497. if lineNearest > 0:
  498. if not self._isSelected(lineNearest):
  499. self.selected['ids'].append(lineNearest)
  500. else:
  501. self.selected['ids'].remove(lineNearest)
  502. px = c_double()
  503. py = c_double()
  504. pz = c_double()
  505. if not self._validLine(lineNearest):
  506. return { 'line' : -1, 'point': None }
  507. ftype = Vect_read_line(poMapInfo, self.poPoints, self.poCats, lineNearest)
  508. Vect_line_distance (self.poPoints, point[0], point[1], 0.0, self.is3D,
  509. byref(px), byref(py), byref(pz),
  510. None, None, None)
  511. # check for duplicates
  512. if self.settings['highlightDupl']['enabled']:
  513. found = poFound.contents
  514. for i in range(found.n_values):
  515. line = found.value[i]
  516. if line != lineNearest:
  517. self.selected['ids'].append(line)
  518. self.GetDuplicates()
  519. for i in range(found.n_values):
  520. line = found.value[i]
  521. if line != lineNearest and not self._isDuplicated(line):
  522. self.selected['ids'].remove(line)
  523. Vect_destroy_list(poFound)
  524. if thisMapInfo:
  525. # drawing segments can be very expensive
  526. # only one features selected
  527. self._drawSegments = True
  528. return { 'line' : lineNearest,
  529. 'point' : (px.value, py.value, pz.value) }
  530. def _listToIList(self, plist):
  531. """!Generate from list struct_ilist
  532. """
  533. ilist = Vect_new_list()
  534. for val in plist:
  535. Vect_list_append(ilist, val)
  536. return ilist
  537. def GetSelectedIList(self, ilist = None):
  538. """!Get list of selected objects as struct_ilist
  539. Returned IList must be freed by Vect_destroy_list().
  540. @return struct_ilist
  541. """
  542. if ilist:
  543. return self._listToIList(ilist)
  544. return self._listToIList(self.selected['ids'])
  545. def GetSelected(self, grassId = True):
  546. """!Get ids of selected objects
  547. @param grassId True for feature id, False for PseudoDC id
  548. @return list of ids of selected vector objects
  549. """
  550. if grassId:
  551. return self.selected['ids']
  552. dc_ids = list()
  553. if not self._drawSegments:
  554. dc_ids.append(1)
  555. elif len(self.selected['ids']) > 0:
  556. # only first selected feature
  557. Vect_read_line(self.poMapInfo, self.poPoints, None,
  558. self.selected['ids'][0])
  559. points = self.poPoints.contents
  560. # node - segment - vertex - segment - node
  561. for i in range(1, 2 * points.n_points):
  562. dc_ids.append(i)
  563. return dc_ids
  564. def SetSelected(self, ids, layer = -1):
  565. """!Set selected vector objects
  566. @param list of ids (None to unselect features)
  567. @param layer layer number for features selected based on category number
  568. """
  569. if ids:
  570. self._drawSelected = True
  571. else:
  572. self._drawSelected = False
  573. self.selected['field'] = layer
  574. if layer > 0:
  575. self.selected['cats'] = ids
  576. self.selected['ids'] = list()
  577. ### cidx is not up-to-date
  578. # Vect_cidx_find_all(self.poMapInfo, layer, GV_POINTS | GV_LINES, lid, ilist)
  579. nlines = Vect_get_num_lines(self.poMapInfo)
  580. for line in range(1, nlines + 1):
  581. if not Vect_line_alive(self.poMapInfo, line):
  582. continue
  583. ltype = Vect_read_line (self.poMapInfo, None, self.poCats, line)
  584. if not (ltype & (GV_POINTS | GV_LINES)):
  585. continue
  586. found = False
  587. cats = self.poCats.contents
  588. for i in range(0, cats.n_cats):
  589. for cat in self.selected['cats']:
  590. if cats.cat[i] == cat:
  591. found = True
  592. break
  593. if found:
  594. self.selected['ids'].append(line)
  595. else:
  596. self.selected['ids'] = ids
  597. self.selected['cats'] = []
  598. def GetSelectedVertex(self, pos):
  599. """!Get PseudoDC vertex id of selected line
  600. Set bounding box for vertices of line.
  601. @param pos position
  602. @return id of center, left and right vertex
  603. @return 0 no line found
  604. @return -1 on error
  605. """
  606. returnId = list()
  607. # only one object can be selected
  608. if len(self.selected['ids']) != 1 or not self._drawSegments:
  609. return returnId
  610. startId = 1
  611. line = self.selected['ids'][0]
  612. if not self._validLine(line):
  613. return -1
  614. ftype = Vect_read_line(self.poMapInfo, self.poPoints, self.poCats, line)
  615. minDist = 0.0
  616. Gid = -1
  617. # find the closest vertex (x, y)
  618. DCid = 1
  619. points = self.poPoints.contents
  620. for idx in range(points.n_points):
  621. dist = Vect_points_distance(pos[0], pos[1], 0.0,
  622. points.x[idx], points.y[idx], points.z[idx], 0)
  623. if idx == 0:
  624. minDist = dist
  625. Gid = idx
  626. else:
  627. if minDist > dist:
  628. minDist = dist
  629. Gid = idx
  630. vx, vy = self._cell2Pixel(points.x[idx], points.y[idx], points.z[idx])
  631. rect = wx.Rect(vx, vy, 0, 0)
  632. self.dc.SetIdBounds(DCid, rect)
  633. DCid += 2
  634. if minDist > self.GetThreshold():
  635. return returnId
  636. # translate id
  637. DCid = Gid * 2 + 1
  638. # add selected vertex
  639. returnId.append(DCid)
  640. # left vertex
  641. if DCid == startId:
  642. returnId.append(-1)
  643. else:
  644. returnId.append(DCid - 2)
  645. # right vertex
  646. if DCid == (points.n_points - 1) * 2 + startId:
  647. returnId.append(-1)
  648. else:
  649. returnId.append(DCid + 2)
  650. return returnId
  651. def GetRegionSelected(self):
  652. """!Get minimal region extent of selected features
  653. @return n,s,w,e
  654. """
  655. regionBox = bound_box()
  656. lineBox = bound_box()
  657. setRegion = True
  658. nareas = Vect_get_num_areas(self.poMapInfo)
  659. for line in self.selected['ids']:
  660. area = Vect_get_centroid_area(self.poMapInfo, line)
  661. if area > 0 and area <= nareas:
  662. if not Vect_get_area_box(self.poMapInfo, area, byref(lineBox)):
  663. continue
  664. else:
  665. if not Vect_get_line_box(self.poMapInfo, line, byref(lineBox)):
  666. continue
  667. if setRegion:
  668. Vect_box_copy(byref(regionBox), byref(lineBox))
  669. setRegion = False
  670. else:
  671. Vect_box_extend(byref(regionBox), byref(lineBox))
  672. return regionBox.N, regionBox.S, regionBox.W, regionBox.E
  673. def DrawSelected(self, flag):
  674. """!Draw selected features
  675. @param flag True to draw selected features
  676. """
  677. self._drawSelected = bool(flag)
  678. def CloseMap(self):
  679. """!Close vector map
  680. @return 0 on success
  681. @return non-zero on error
  682. """
  683. ret = 0
  684. if self.poMapInfo:
  685. # rebuild topology
  686. Vect_build_partial(self.poMapInfo, GV_BUILD_NONE)
  687. Vect_build(self.poMapInfo)
  688. # close map and store topo/cidx
  689. ret = Vect_close(self.poMapInfo)
  690. del self.mapInfo
  691. self.poMapInfo = self.mapInfo = None
  692. return ret
  693. def OpenMap(self, name, mapset, update = True):
  694. """!Open vector map by the driver
  695. @param name name of vector map to be open
  696. @param mapset name of mapset where the vector map lives
  697. @return map_info
  698. @return None on error
  699. """
  700. Debug.msg("DisplayDriver.OpenMap(): name=%s mapset=%s updated=%d",
  701. name, mapset, update)
  702. if not self.mapInfo:
  703. self.mapInfo = Map_info()
  704. self.poMapInfo = pointer(self.mapInfo)
  705. # open existing map
  706. if update:
  707. ret = Vect_open_update(self.poMapInfo, name, mapset)
  708. Vect_set_updated(self.poMapInfo, True) # track updated lines
  709. else:
  710. ret = Vect_open_old(self.poMapInfo, name, mapset)
  711. self.is3D = Vect_is_3d(self.poMapInfo)
  712. if ret == -1: # error
  713. del self.mapInfo
  714. self.poMapInfo = self.mapInfo = None
  715. elif ret < 2:
  716. dlg = wx.MessageDialog(parent = self.window,
  717. message = _("Topology for vector map <%s> is not available. "
  718. "Topology is required by digitizer. Do you want to "
  719. "rebuild topology (takes some time) and open the vector map "
  720. "for editing?") % name,
  721. caption=_("Topology missing"),
  722. style = wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION | wx.CENTRE)
  723. ret = dlg.ShowModal()
  724. if ret != wx.ID_YES:
  725. del self.mapInfo
  726. self.poMapInfo = self.mapInfo = None
  727. else:
  728. Vect_build(self.poMapInfo)
  729. return self.poMapInfo
  730. def GetMapBoundingBox(self):
  731. """!Get bounding box of (opened) vector map layer
  732. @return (w,s,b,e,n,t)
  733. """
  734. if not self.poMapInfo:
  735. return None
  736. bbox = bound_box()
  737. Vect_get_map_box(self.poMapInfo, byref(bbox))
  738. return bbox.W, bbox.S, bbox.B, \
  739. bbox.E, bbox.N, bbox.T
  740. def UpdateSettings(self, alpha = 255):
  741. """!Update display driver settings
  742. @todo map units
  743. @param alpha color value for aplha channel
  744. """
  745. color = dict()
  746. for key in self.settings.keys():
  747. if key == 'lineWidth':
  748. self.settings[key] = int(UserSettings.Get(group = 'vdigit', key = 'lineWidth',
  749. subkey = 'value'))
  750. continue
  751. color = wx.Colour(UserSettings.Get(group = 'vdigit', key = 'symbol',
  752. subkey = [key, 'color'])[0],
  753. UserSettings.Get(group = 'vdigit', key = 'symbol',
  754. subkey = [key, 'color'])[1],
  755. UserSettings.Get(group = 'vdigit', key = 'symbol',
  756. subkey = [key, 'color'])[2],
  757. alpha)
  758. if key == 'highlight':
  759. self.settings[key] = color
  760. continue
  761. if key == 'highlightDupl':
  762. self.settings[key]['enabled'] = bool(UserSettings.Get(group = 'vdigit', key = 'checkForDupl',
  763. subkey = 'enabled'))
  764. else:
  765. self.settings[key]['enabled'] = bool(UserSettings.Get(group = 'vdigit', key = 'symbol',
  766. subkey = [key, 'enabled']))
  767. self.settings[key]['color'] = color
  768. def UpdateRegion(self):
  769. """!Update geographical region used by display driver
  770. """
  771. self.region = self.mapObj.GetCurrentRegion()
  772. def GetThreshold(self, type = 'snapping', value = None, units = None):
  773. """!Return threshold value in map units
  774. @param type snapping mode (node, vertex)
  775. @param value threshold to be set up
  776. @param units units (map, screen)
  777. @return threshold value
  778. """
  779. if value is None:
  780. value = UserSettings.Get(group = 'vdigit', key = type, subkey = 'value')
  781. if units is None:
  782. units = UserSettings.Get(group = 'vdigit', key = type, subkey = 'units')
  783. if value < 0:
  784. value = (self.region['nsres'] + self.region['ewres']) / 2.0
  785. if units == "screen pixels":
  786. # pixel -> cell
  787. res = max(self.region['nsres'], self.region['ewres'])
  788. return value * res
  789. return value
  790. def GetDuplicates(self):
  791. """!Return ids of (selected) duplicated vector features
  792. """
  793. if not self.poMapInfo:
  794. return
  795. ids = dict()
  796. APoints = Vect_new_line_struct()
  797. BPoints = Vect_new_line_struct()
  798. self.selected['idsDupl'] = list()
  799. for i in range(len(self.selected['ids'])):
  800. line1 = self.selected['ids'][i]
  801. if self._isDuplicated(line1):
  802. continue
  803. Vect_read_line(self.poMapInfo, APoints, None, line1)
  804. for line2 in self.selected['ids']:
  805. if line1 == line2 or self._isDuplicated(line2):
  806. continue
  807. Vect_read_line(self.poMapInfo, BPoints, None, line2)
  808. if Vect_line_check_duplicate(APoints, BPoints, WITHOUT_Z):
  809. if i not in ids:
  810. ids[i] = list()
  811. ids[i].append((line1, self._getCatString(line1)))
  812. self.selected['idsDupl'].append(line1)
  813. ids[i].append((line2, self._getCatString(line2)))
  814. self.selected['idsDupl'].append(line2)
  815. Vect_destroy_line_struct(APoints)
  816. Vect_destroy_line_struct(BPoints)
  817. return ids
  818. def _getCatString(self, line):
  819. Vect_read_line(self.poMapInfo, None, self.poCats, line)
  820. cats = self.poCats.contents
  821. catsDict = dict()
  822. for i in range(cats.n_cats):
  823. layer = cats.field[i]
  824. if layer not in catsDict:
  825. catsDict[layer] = list()
  826. catsDict[layer].append(cats.cat[i])
  827. catsStr = ''
  828. for l, c in catsDict.iteritems():
  829. catsStr = '%d: (%s)' % (l, ','.join(map(str, c)))
  830. return catsStr
  831. def UnSelect(self, lines):
  832. """!Unselect vector features
  833. @param lines list of feature id(s)
  834. """
  835. checkForDupl = False
  836. for line in lines:
  837. if self._isSelected(line):
  838. self.selected['ids'].remove(line)
  839. if self.settings['highlightDupl']['enabled'] and self._isDuplicated(line):
  840. checkForDupl = True
  841. if checkForDupl:
  842. self.GetDuplicates()
  843. return len(self.selected['ids'])