wxdisplay.py 36 KB

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