GRASS Programmer's Manual  6.4.2(2012)
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
wxvdigit.py
Go to the documentation of this file.
1 """!
2 @package wxvdigit.py
3 
4 @brief wxGUI vector digitizer (base class)
5 
6 Code based on wxVdigit C++ component from GRASS 6.4.0
7 (gui/wxpython/vdigit). Converted to Python in 2010/12-2011/01.
8 
9 List of classes:
10  - VDigitError
11  - IVDigit
12 
13 (C) 2007-2011 by the GRASS Development Team
14 
15 This program is free software under the GNU General Public License
16 (>=v2). Read the file COPYING that comes with GRASS for details.
17 
18 @author Martin Landa <landa.martin gmail.com>
19 """
20 
21 from gcmd import GError
22 from debug import Debug
23 from preferences import globalSettings as UserSettings
24 
25 from wxvdriver import DisplayDriver
26 
27 from grass.lib.gis import *
28 from grass.lib.vector import *
29 from grass.lib.vedit import *
30 from grass.lib.dbmi import *
31 
33  def __init__(self, parent):
34  """!Class for managing error messages of vector digitizer
35 
36  @param parent parent window for dialogs
37  """
38  self.parent = parent
39  self.caption = _('Digitization Error')
40 
41  def NoMap(self, name = None):
42  """!No map for editing"""
43  if name:
44  message = _('Unable to open vector map <%s>.') % name
45  else:
46  message = _('No vector map open for editing.')
47  GError(message + ' ' + _('Operation cancelled.'),
48  parent = self.parent,
49  caption = self.caption)
50 
51  def WriteLine(self):
52  """!Writing line failed
53  """
54  GError(message = _('Writing new feature failed. '
55  'Operation cancelled.'),
56  parent = self.parent,
57  caption = self.caption)
58 
59  def ReadLine(self, line):
60  """!Reading line failed
61  """
62  GError(message = _('Reading feature id %d failed. '
63  'Operation cancelled.') % line,
64  parent = self.parent,
65  caption = self.caption)
66 
67  def DbLink(self, dblink):
68  """!No dblink available
69  """
70  GError(message = _('Database link %d not available. '
71  'Operation cancelled.') % dblink,
72  parent = self.parent,
73  caption = self.caption)
74 
75  def Driver(self, driver):
76  """!Staring driver failed
77  """
78  GError(message = _('Unable to start database driver <%s>. '
79  'Operation cancelled.') % driver,
80  parent = self.parent,
81  caption = self.caption)
82 
83  def Database(self, driver, database):
84  """!Opening database failed
85  """
86  GError(message = _('Unable to open database <%(db)s> by driver <%(driver)s>. '
87  'Operation cancelled.') % { 'db' : database, 'driver' : driver},
88  parent = self.parent,
89  caption = self.caption)
90 
91  def DbExecute(self, sql):
92  """!Sql query failed
93  """
94  GError(message = _("Unable to execute SQL query '%s'. "
95  "Operation cancelled.") % sql,
96  parent = self.parent,
97  caption = self.caption)
98 
99  def DeadLine(self, line):
100  """!Dead line
101  """
102  GError(message = _("Feature id %d is marked as dead. "
103  "Operation cancelled.") % line,
104  parent = self.parent,
105  caption = self.caption)
106 
107  def FeatureType(self, ftype):
108  """!Unknown feature type
109  """
110  GError(message = _("Unsupported feature type %d. "
111  "Operation cancelled.") % ftype,
112  parent = self.parent,
113  caption = self.caption)
114 
115 class IVDigit:
116  def __init__(self, mapwindow):
117  """!Base class for vector digitizer (ctypes interface)
118 
119  @parem mapwindow reference for map window (BufferedWindow)
120  """
121  self.poMapInfo = None # pointer to Map_info
122  self.mapWindow = mapwindow
123 
124  # background map
125  self.bgMapInfo = Map_info()
126  self.poBgMapInfo = self.popoBgMapInfo = None
127 
128  if not mapwindow.parent.IsStandalone():
129  goutput = mapwindow.parent.GetLayerManager().GetLogWindow()
130  log = goutput.GetLog(err = True)
131  progress = goutput.GetProgressBar()
132  else:
133  log = sys.stderr
134  progress = None
135 
136  self.toolbar = mapwindow.parent.toolbars['vdigit']
137 
138  self._error = VDigitError(parent = self.mapWindow)
139 
140  self._display = DisplayDriver(device = mapwindow.pdcVector,
141  deviceTmp = mapwindow.pdcTmp,
142  mapObj = mapwindow.Map,
143  window = mapwindow,
144  glog = log,
145  gprogress = progress)
146 
147  # GRASS lib
150 
151  # self.SetCategory()
152 
153  # layer / max category
154  self.cats = dict()
155 
156  self._settings = dict()
157  self.UpdateSettings() # -> self._settings
158 
159  # undo/redo
160  self.changesets = dict()
161  self.changesetCurrent = -1 # first changeset to apply
162  self.changesetEnd = -1 # last changeset to be applied
163 
164  if self.poMapInfo:
165  self.InitCats()
166 
167  def __del__(self):
168  Debug.msg(1, "IVDigit.__del__()")
170  self.poPoints = None
172  self.poCats = None
173 
174  if self.poBgMapInfo:
175  Vect_close(self.poBgMapInfo)
176  self.poBgMapInfo = self.popoBgMapInfo = None
177  del self.bgMapInfo
178 
180  """!Close background vector map"""
181  if not self.poBgMapInfo:
182  return
183 
184  Vect_close(self.poBgMapInfo)
185  self.poBgMapInfo = self.popoBgMapInfo = None
186 
187  def OpenBackgroundMap(self, bgmap):
188  """!Open background vector map
189 
190  @todo support more background maps then only one
191 
192  @param bgmap name of vector map to be opened
193 
194  @return pointer to map_info
195  @return None on error
196  """
197  name = create_string_buffer(GNAME_MAX)
198  mapset = create_string_buffer(GMAPSET_MAX)
199  if not G__name_is_fully_qualified(bgmap, name, mapset):
200  name = str(bgmap)
201  mapset = str(G_find_vector2(bgmap, ''))
202  else:
203  name = str(name.value)
204  mapset = str(mapset.value)
205 
206  if (name == Vect_get_name(self.poMapInfo) and \
207  mapset == Vect_get_mapset(self.poMapInfo)):
208  self.poBgMapInfo = self.popoBgMapInfo = None
209  self._error.NoMap(bgmap)
210  return
211 
212  self.poBgMapInfo = pointer(self.bgMapInfo)
213  self.popoBgMapInfo = pointer(self.poBgMapInfo)
214  if Vect_open_old(self.poBgMapInfo, name, mapset) == -1:
215  self.poBgMapInfo = self.popoBgMapInfo = None
216  self._error.NoMap(bgmap)
217  return
218 
219  def _getSnapMode(self):
220  """!Get snapping mode
221 
222  - snap to vertex
223  - snap to nodes
224  - no snapping
225 
226  @return snap mode
227  """
228  threshold = self._display.GetThreshold()
229  if threshold > 0.0:
230  if UserSettings.Get(group = 'vdigit', key = 'snapToVertex', subkey = 'enabled'):
231  return SNAPVERTEX
232  else:
233  return SNAP
234  else:
235  return NO_SNAP
236 
237  def _breakLineAtIntersection(self, line, pointsLine, changeset):
238  """!Break given line at intersection
239 
240  \param line line id
241  \param pointsLine line geometry
242  \param changeset id
243 
244  \return number of modified lines
245  """
246  if not self._checkMap():
247  return -1
248 
249  if not Vect_line_alive(self.poMapInfo, line):
250  return 0
251 
252  if not pointsLine:
253  if Vect_read_line(self.poMapInfo, self.poPoints, None, line) < 0:
254  self._error.ReadLine(line)
255  return -1
256  points = self.poPoints
257  else:
258  points = pointsLine
259 
260  listLine = Vect_new_list()
261  listRef = Vect_new_list()
262  listBreak = Vect_new_list()
263 
264  pointsCheck = Vect_new_line_struct()
265 
266  lineBox = bound_box()
267  # find all relevant lines
268  Vect_get_line_box(self.poMapInfo, line, byref(lineBox))
269  Vect_select_lines_by_box(self.poMapInfo, byref(lineBox),
270  GV_LINES, listLine)
271 
272  # check for intersection
273  Vect_list_append(listBreak, line)
274  Vect_list_append(listRef, line)
275  for i in range(listLine.contents.n_values):
276  lineBreak = listLine.contents.value[i]
277  if lineBreak == line:
278  continue
279 
280  ltype = Vect_read_line(self.poMapInfo, pointsCheck, None, lineBreak)
281  if not (ltype & GV_LINES):
282  continue
283 
284  if Vect_line_check_intersection(self.poPoints, pointsCheck,
285  WITHOUT_Z):
286  Vect_list_append(listBreak, lineBreak)
287 
288  nlines = Vect_get_num_lines(self.poMapInfo)
289 
290  for i in range(listBreak.contents.n_values):
291  self._addActionToChangeset(changeset, listBreak.contents.value[i], add = False)
292 
293  ret = Vect_break_lines_list(self.poMapInfo, listBreak, listRef,
294  GV_LINES, None)
295 
296  for i in range(listBreak.contents.n_values):
297  if Vect_line_alive(self.poMapInfo, listBreak.contents.value[i]):
298  self._removeActionFromChangeset(changeset, listBreak.contents.value[i],
299  add = False)
300 
301  for line in range(nlines + 1, Vect_get_num_lines(self.poMapInfo) + 1):
302  self._addActionToChangeset(changeset, line, add = True)
303 
304  Vect_destroy_line_struct(pointsCheck)
305 
306  Vect_destroy_list(listLine)
307  Vect_destroy_list(listBreak)
308  Vect_destroy_list(listRef)
309 
310  return ret
311 
312  def _addActionsBefore(self):
313  """!Register action before operation
314 
315  @return changeset id
316  """
317  changeset = len(self.changesets)
318  for line in self._display.selected['ids']:
319  if Vect_line_alive(self.poMapInfo, line):
320  self._addActionToChangeset(changeset, line, add = False)
321 
322  return changeset
323 
324  def _applyChangeset(self, changeset, undo):
325  """!Apply changeset (undo/redo changeset)
326 
327  @param changeset changeset id
328  @param undo True for undo otherwise redo
329 
330  @return 1 changeset applied
331  @return 0 changeset not applied
332  @return -1 on error
333  """
334  if changeset < 0 or changeset > len(self.changesets.keys()):
335  return -1
336 
337  if self.changesetEnd < 0:
338  self.changesetEnd = changeset
339 
340  ret = 0
341  actions = self.changesets[changeset]
342  for action in actions:
343  add = action['add']
344  line = action['line']
345  if (undo and add) or \
346  (not undo and not add):
347  if Vect_line_alive(self.poMapInfo, line):
348  Debug.msg(3, "IVDigit._applyChangeset(): changeset=%d, action=add, line=%d -> deleted",
349  changeset, line)
350  Vect_delete_line(self.poMapInfo, line)
351  ret = 1
352  else:
353  Debug.msg(3, "Digit.ApplyChangeset(): changeset=%d, action=add, line=%d dead",
354  changeset, line)
355  else: # delete
356  offset = action['offset']
357  if not Vect_line_alive(self.poMapInfo, line):
358  Debug.msg(3, "Digit.ApplyChangeset(): changeset=%d, action=delete, line=%d -> added",
359  changeset, line)
360  if Vect_restore_line(self.poMapInfo, line, offset) < 0:
361  return -1
362  ret = 1
363  else:
364  Debug.msg(3, "Digit.ApplyChangeset(): changeset=%d, action=delete, line=%d alive",
365  changeset, line)
366 
367  return ret
368 
369  def _addActionsAfter(self, changeset, nlines):
370  """!Register action after operation
371 
372  @param changeset changeset id
373  @param nline number of lines
374  """
375  for line in self._display.selected['ids']:
376  if Vect_line_alive(self.poMapInfo, line):
377  self._removeActionFromChangeset(changeset, line, add = False)
378 
379  for line in range(nlines + 1, Vect_get_num_lines(self.poMapInfo)):
380  if Vect_line_alive(self.poMapInfo, line):
381  self._addActionToChangeset(changeset, line, add = True)
382 
383  def _addActionToChangeset(self, changeset, line, add):
384  """!Add action to changeset
385 
386  @param changeset id of changeset
387  @param line feature id
388  @param add True to add, otherwise delete
389  """
390  if not self._checkMap():
391  return
392 
393  if not Vect_line_alive(self.poMapInfo, line):
394  return
395 
396  offset = Vect_get_line_offset(self.poMapInfo, line)
397 
398  if changeset not in self.changesets:
399  self.changesets[changeset] = list()
400  self.changesetCurrent = changeset
401 
402  self.changesets[changeset].append({ 'add' : add,
403  'line' : line,
404  'offset' : offset })
405 
406  Debug.msg(3, "IVDigit._addActionToChangeset(): changeset=%d, add=%d, line=%d, offset=%d",
407  changeset, add, line, offset)
408 
409  def _removeActionFromChangeset(self, changeset, line, add):
410  """!Remove action from changeset
411 
412  @param changeset changeset id
413  @param line line id
414  @param add True for add, False for delete
415 
416  @return number of actions in changeset
417  @return -1 on error
418  """
419  if changeset not in self.changesets.keys():
420  return -1
421 
422  alist = self.changesets[changeset]
423  for action in alist:
424  if action['add'] == add and action['line'] == line:
425  alist.remove(action)
426 
427  return len(alist)
428 
429  def AddFeature(self, ftype, points):
430  """!Add new feature
431 
432  @param ftype feature type (point, line, centroid, boundary)
433  @param points tuple of points ((x, y), (x, y), ...)
434 
435  @return tuple (number of added features, feature ids)
436  """
437  if UserSettings.Get(group = 'vdigit', key = "categoryMode", subkey = 'selection') == 2:
438  layer = -1 # -> no category
439  cat = -1
440  else:
441  layer = UserSettings.Get(group = 'vdigit', key = "layer", subkey = 'value')
442  cat = self.SetCategory()
443 
444  if ftype == 'point':
445  vtype = GV_POINT
446  elif ftype == 'line':
447  vtype = GV_LINE
448  elif ftype == 'centroid':
449  vtype = GV_CENTROID
450  elif ftype == 'boundary':
451  vtype = GV_BOUNDARY
452  elif ftype == 'area':
453  vtype = GV_AREA
454  else:
455  GError(parent = self.mapWindow,
456  message = _("Unknown feature type '%s'") % ftype)
457  return (-1, None)
458 
459  if vtype & GV_LINES and len(points) < 2:
460  GError(parent = self.mapWindow,
461  message = _("Not enough points for line"))
462  return (-1, None)
463 
464  self.toolbar.EnableUndo()
465 
466  return self._addFeature(vtype, points, layer, cat,
467  self._getSnapMode(), self._display.GetThreshold())
468 
470  """!Delete selected features
471 
472  @return number of deleted features
473  """
474  deleteRec = UserSettings.Get(group = 'vdigit', key = 'delRecord', subkey = 'enabled')
475  if not self._checkMap():
476  return -1
477 
478  n_dblinks = Vect_get_num_dblinks(self.poMapInfo)
479  Cats_del = None
480 
481  # collect categories for delete if requested
482  if deleteRec:
483  poCats = Vect_new_cats_struct()
484  poCatsDel = Vect_new_cats_struct()
485  for i in self._display.selected['ids']:
486  if Vect_read_line(self.poMapInfo, None, poCats, i) < 0:
487  Vect_destroy_cats_struct(poCatsDel)
488  self._error.ReadLine(i)
489  return -1
490 
491  cats = poCats.contents
492  for j in range(cats.n_cats):
493  Vect_cat_set(poCatsDel, cats.field[j], cats.cat[j])
494 
496 
497  # register changeset
498  changeset = self._addActionsBefore()
499 
500  poList = self._display.GetSelectedIList()
501  nlines = Vedit_delete_lines(self.poMapInfo, poList)
502  Vect_destroy_list(poList)
503  self._display.selected['ids'] = list()
504 
505  if nlines > 0 and deleteRec:
506  handle = dbHandle()
507  poHandle = pointer(handle)
508  stmt = dbString()
509  poStmt = pointer(stmt)
510 
511  for dblink in range(n_dblinks):
512  poFi = Vect_get_dblink(self.poMapInfo, dblink)
513  if poFi is None:
514  self._error.DbLink(dblink)
515  return -1
516 
517  Fi = poFi.contents
518  poDriver = db_start_driver(Fi.driver)
519  if poDriver is None:
520  self._error.Driver(Fi.driver)
521  return -1
522 
523  db_init_handle(poHandle)
524  db_set_handle(poHandle, Fi.database, None)
525  if db_open_database(poDriver, poHandle) != DB_OK:
526  self._error.Database(Fi.driver, Fi.database)
527  return -1
528 
529  db_init_string(poStmt)
530  db_set_string(poStmt, "DELETE FROM %s WHERE" % Fi.table)
531  n_cats = 0;
532  catsDel = poCatsDel.contents
533  for c in range(catsDel.n_cats):
534  if catsDel.field[c] == Fi.number:
535  if n_cats > 0:
536  db_append_string(poStmt, " or")
537 
538  db_append_string(poStmt, " %s = %d" % (Fi.key, catsDel.cat[c]))
539  n_cats += 1
540 
541  Vect_cat_del(poCatsDel, Fi.number)
542 
543  if n_cats and \
544  db_execute_immediate(poDriver, poStmt) != DB_OK:
545  self._error.DbExecute(db_get_string(poStmt))
546  return -1
547 
548  db_close_database(poDriver)
549  db_shutdown_driver(poDriver)
550 
551  if poCatsDel:
552  Vect_destroy_cats_struct(poCatsDel)
553 
554  if nlines > 0:
555  self.toolbar.EnableUndo()
556 
557  return nlines
558 
559  def MoveSelectedLines(self, move):
560  """!Move selected features
561 
562  @param move direction (x, y)
563  """
564  if not self._checkMap():
565  return -1
566 
567  thresh = self._display.GetThreshold()
568  snap = self._getSnapMode()
569 
570  nlines = Vect_get_num_lines(self.poMapInfo)
571 
572  # register changeset
573  changeset = self._addActionsBefore()
574 
575  poList = self._display.GetSelectedIList()
576  nlines = Vedit_move_lines(self.poMapInfo, self.popoBgMapInfo, int(self.poBgMapInfo is not None),
577  poList,
578  move[0], move[1], 0,
579  snap, thresh)
580  Vect_destroy_list(poList)
581 
582  if nlines > 0:
583  self._addActionsAfter(changeset, nlines)
584  else:
585  del self.changesets[changeset]
586 
587  if nlines > 0 and self._settings['breakLines']:
588  for i in range(1, nlines):
589  self._breakLineAtIntersection(nlines + i, None, changeset)
590 
591  if nlines > 0:
592  self.toolbar.EnableUndo()
593 
594  return nlines
595 
596  def MoveSelectedVertex(self, point, move):
597  """!Move selected vertex of the line
598 
599  @param point location point
600  @param move x,y direction
601 
602  @return id of new feature
603  @return 0 vertex not moved (not found, line is not selected)
604  @return -1 on error
605  """
606  if not self._checkMap():
607  return -1
608 
609  if len(self._display.selected['ids']) != 1:
610  return -1
611 
613  Vect_append_point(self.poPoints, point[0], point[1], 0.0)
614 
615  nlines = Vect_get_num_lines(self.poMapInfo)
616 
617  changeset = self._addActionsBefore()
618 
619  # move only first found vertex in bbox
620  poList = self._display.GetSelectedIList()
621  moved = Vedit_move_vertex(self.poMapInfo, self.popoBgMapInfo, int(self.poBgMapInfo is not None),
622  poList, self.poPoints,
623  self._display.GetThreshold(type = 'selectThresh'),
624  self._display.GetThreshold(),
625  move[0], move[1], 0.0,
626  1, self._getSnapMode())
627  Vect_destroy_list(poList)
628 
629  if moved > 0:
630  self._addActionsAfter(changeset, nlines)
631  else:
632  del self.changesets[changeset]
633 
634  if moved > 0 and self._settings['breakLines']:
636  None, changeset)
637 
638  if moved > 0:
639  self.toolbar.EnableUndo()
640 
641  return moved
642 
643  def AddVertex(self, coords):
644  """!Add new vertex to the selected line/boundary on position 'coords'
645 
646  @param coords coordinates to add vertex
647 
648  @return id of new feature
649  @return 0 nothing changed
650  @return -1 on failure
651  """
652  added = self._ModifyLineVertex(coords, add = True)
653 
654  if added > 0:
655  self.toolbar.EnableUndo()
656 
657  return added
658 
659  def RemoveVertex(self, coords):
660  """!Remove vertex from the selected line/boundary on position 'coords'
661 
662  @param coords coordinates to remove vertex
663 
664  @return id of new feature
665  @return 0 nothing changed
666  @return -1 on failure
667  """
668  deleted = self._ModifyLineVertex(coords, add = False)
669 
670  if deleted > 0:
671  self.toolbar.EnableUndo()
672 
673  return deleted
674 
675 
676  def SplitLine(self, point):
677  """!Split/break selected line/boundary on given position
678 
679  @param point point where to split line
680 
681  @return 1 line modified
682  @return 0 nothing changed
683  @return -1 error
684  """
685  thresh = self._display.GetThreshold('selectThresh')
686  if not self._checkMap():
687  return -1
688 
689  poList = self._display.GetSelectedIList()
691  Vect_append_point(self.poPoints, point[0], point[1], 0.0)
692 
693  nlines = Vect_get_num_lines(self.poMapInfo)
694 
695  changeset = self._addActionsBefore()
696 
697  ret = Vedit_split_lines(self.poMapInfo, poList,
698  self.poPoints, thresh, None)
699  Vect_destroy_list(poList)
700 
701  if ret > 0:
702  self._addActionsAfter(changeset, nlines)
703  self.toolbar.EnableUndo()
704  else:
705  del self.changesets[changeset]
706 
707  return ret
708 
709  def EditLine(self, line, coords):
710  """!Edit existing line/boundary
711 
712  @param line feature id to be modified
713  @param coords list of coordinates of modified line
714 
715  @return feature id of new line
716  @return -1 on error
717  """
718  if not self._checkMap():
719  return -1
720 
721  if len(coords) < 2:
722  self.DeleteSelectedLines()
723  return 0
724 
725  if not Vect_line_alive(self.poMapInfo, line):
726  self._error.DeadLine(line)
727  return -1
728 
729  # read original feature
730  ltype = Vect_read_line(self.poMapInfo, None, self.poCats, line)
731  if ltype < 0:
732  self._error.ReadLine(line)
733  return -1
734 
735  # build feature geometry
737  for p in coords:
738  Vect_append_point(self.poPoints, p[0], p[1], 0.0)
739 
740  # apply snapping (node or vertex)
741  snap = self._getSnapMode()
742  if snap != NO_SNAP:
743  modeSnap = not (snap == SNAP)
745  int(self.poBgMapInfo is not None),
746  -1, self.poPoints, self._display.GetThreshold(), modeSnap)
747 
748  nlines = Vect_get_num_lines(self.poMapInfo)
749 
750  changeset = self._addActionsBefore()
751  newline = Vect_rewrite_line(self.poMapInfo, line, ltype,
752  self.poPoints, self.poCats)
753  if newline > 0:
754  self._addActionsAfter(changeset, nlines)
755  self.toolbar.EnableUndo()
756  else:
757  del self.changesets[changeset]
758 
759  if newline > 0 and self._settings['breakLines']:
760  self._breakLineAtIntersection(newline, None, changeset)
761 
762  return newline
763 
764  def FlipLine(self):
765  """!Flip selected lines/boundaries
766 
767  @return number of modified lines
768  @return -1 on error
769  """
770  if not self._checkMap():
771  return -1
772 
773  nlines = Vect_get_num_lines(self.poMapInfo)
774 
775  # register changeset
776  changeset = self._addActionsBefore()
777 
778  poList = self._display.GetSelectedIList()
779  ret = Vedit_flip_lines(self.poMapInfo, poList)
780  Vect_destroy_list(poList)
781 
782  if ret > 0:
783  self._addActionsAfter(changeset, nlines)
784  self.toolbar.EnableUndo()
785  else:
786  del self.changesets[changeset]
787 
788  return ret
789 
790  def MergeLine(self):
791  """!Merge selected lines/boundaries
792 
793  @return number of modified lines
794  @return -1 on error
795  """
796  if not self._checkMap():
797  return -1
798 
799  nlines = Vect_get_num_lines(self.poMapInfo)
800 
801  changeset = self._addActionsBefore()
802 
803  poList = self._display.GetSelectedIList()
804  ret = Vedit_merge_lines(self.poMapInfo, poList)
805  Vect_destroy_list(poList)
806 
807  if ret > 0:
808  self._addActionsAfter(changeset, nlines)
809  self.toolbar.EnableUndo()
810  else:
811  del self.changesets[changeset]
812 
813  return ret
814 
815  def BreakLine(self):
816  """!Break selected lines/boundaries
817 
818  @return number of modified lines
819  @return -1 on error
820  """
821  if not self._checkMap():
822  return -1
823 
824  nlines = Vect_get_num_lines(self.poMapInfo)
825 
826  changeset = self._addActionsBefore()
827 
828  poList = self._display.GetSelectedIList()
829  ret = Vect_break_lines_list(self.poMapInfo, poList, None,
830  GV_LINES, None)
831  Vect_destroy_list(poList)
832 
833  if ret > 0:
834  self._addActionsAfter(changeset, nlines)
835  self.toolbar.EnableUndo()
836  else:
837  del self.changesets[changeset]
838 
839  return ret
840 
841  def SnapLine(self):
842  """!Snap selected lines/boundaries
843 
844  @return on success
845  @return -1 on error
846  """
847  if not self._checkMap():
848  return -1
849 
850  nlines = Vect_get_num_lines(self.poMapInfo)
851 
852  changeset = self._addActionsBefore()
853 
854  poList = self._display.GetSelectedIList()
855  Vect_snap_lines_list(self.poMapInfo, poList,
856  self._display.GetThreshold(), None)
857  Vect_destroy_list(poList)
858 
859  if nlines < Vect_get_num_lines(self.poMapInfo):
860  self._addActionsAfter(changeset, nlines)
861  self.toolbar.EnableUndo()
862  else:
863  del self.changesets[changeset]
864 
865  def ConnectLine(self):
866  """!Connect selected lines/boundaries
867 
868  @return 1 lines connected
869  @return 0 lines not connected
870  @return -1 on error
871  """
872  if not self._checkMap():
873  return -1
874 
875  nlines = Vect_get_num_lines(self.poMapInfo)
876 
877  # register changeset
878  changeset = self._addActionsBefore()
879 
880  poList = self._display.GetSelectedIList()
881  ret = Vedit_connect_lines(self.poMapInfo, poList,
882  self._display.GetThreshold())
883  Vect_destroy_list(poList)
884 
885  if ret > 0:
886  self._addActionsAfter(changeset, nlines)
887  self.toolbar.EnableUndo()
888  else:
889  del self.changesets[changeset]
890 
891  return ret
892 
893  def CopyLine(self, ids = []):
894  """!Copy features from (background) vector map
895 
896  @param ids list of line ids to be copied
897 
898  @return number of copied features
899  @return -1 on error
900  """
901  if not self._checkMap():
902  return -1
903 
904  nlines = Vect_get_num_lines(self.poMapInfo)
905 
906  poList = self._display.GetSelectedIList(ids)
907  ret = Vedit_copy_lines(self.poMapInfo, self.poBgMapInfo,
908  poList)
909  Vect_destroy_list(poList)
910 
911  if ret > 0:
912  changeset = len(self.changesets)
913  for line in (range(nlines + 1, Vect_get_num_lines(self.poMapInfo))):
914  self._addActionToChangeset(changeset, line, add = True)
915  self.toolbar.EnableUndo()
916  else:
917  del self.changesets[changeset]
918 
919  if ret > 0 and self.poBgMapInfo and self._settings['breakLines']:
920  for i in range(1, ret):
921  self._breakLineAtIntersection(nlines + i, None, changeset)
922 
923  return ret
924 
925  def CopyCats(self, fromId, toId, copyAttrb = False):
926  """!Copy given categories to objects with id listed in ids
927 
928  @param cats ids of 'from' feature
929  @param ids ids of 'to' feature(s)
930 
931  @return number of modified features
932  @return -1 on error
933  """
934  if len(fromId) < 1 or len(toId) < 1:
935  return 0
936 
937  poCatsFrom = self.poCats
938  poCatsTo = Vect_new_cats_struct();
939 
940  nlines = 0
941 
942  for fline in fromId:
943  if not Vect_line_alive(self.poMapInfo, fline):
944  continue
945 
946  if Vect_read_line(self.poMapInfo, None, poCatsFrom, fline) < 0:
947  self._error.ReadLine(fline)
948  return -1
949 
950  for tline in toId:
951  if not Vect_line_alive(self.poMapInfo, tline):
952  continue
953 
954  ltype = Vect_read_line(self.poMapInfo, self.poPoints, poCatsTo, tline)
955  if ltype < 0:
956  self._error.ReadLine(fline)
957  return -1
958 
959  catsFrom = poCatsFrom.contents
960  for i in range(catsFrom.n_cats):
961  if not copyAttrb:
962  # duplicate category
963  cat = catsFrom.cat[i]
964  else:
965  # duplicate attributes
966  cat = self.cats[catsFrom.field[i]] + 1
967  self.cats[catsFrom.field[i]] = cat
968  poFi = Vect_get_field(self.poMapInfo, catsFrom.field[i])
969  if not poFi:
970  self._error.DbLink(i)
971  return -1
972 
973  fi = poFi.contents
974  driver = db_start_driver(fi.driver)
975  if not driver:
976  self._error.Driver(fi.driver)
977  return -1
978 
979  handle = dbHandle()
980  db_init_handle(byref(handle))
981  db_set_handle(byref(handle), fi.database, None)
982  if db_open_database(driver, byref(handle)) != DB_OK:
983  db_shutdown_driver(driver)
984  self._error.Database(fi.driver, fi.database)
985  return -1
986 
987  stmt = dbString()
988  db_init_string(byref(stmt))
989  db_set_string(byref(stmt),
990  "SELECT * FROM %s WHERE %s=%d" % (fi.table, fi.key,
991  catsFrom.cat[i]))
992 
993  cursor = dbCursor()
994  if db_open_select_cursor(driver, byref(stmt), byref(cursor),
995  DB_SEQUENTIAL) != DB_OK:
997  return -1
998 
999  table = db_get_cursor_table(byref(cursor))
1000  ncols = db_get_table_number_of_columns(table)
1001 
1002  sql = "INSERT INTO %s VALUES (" % fi.table
1003  # fetch the data
1004  more = c_int()
1005  while True:
1006  if db_fetch(byref(cursor), DB_NEXT, byref(more)) != DB_OK:
1008  return -1
1009  if not more.value:
1010  break
1011 
1012  value_string = dbString()
1013  for col in range(ncols):
1014  if col > 0:
1015  sql += ","
1016 
1017  column = db_get_table_column(table, col)
1018  if db_get_column_name(column) == fi.key:
1019  sql += "%d" % cat
1020  continue
1021 
1022  value = db_get_column_value(column)
1023  db_convert_column_value_to_string(column, byref(value_string))
1024  if db_test_value_isnull(value):
1025  sql += "NULL"
1026  else:
1028  if ctype != DB_C_TYPE_STRING:
1029  sql += db_get_string(byref(value_string))
1030  else:
1031  sql += "'%s'" % db_get_string(byref(value_string))
1032 
1033  sql += ")"
1034  db_set_string(byref(stmt), sql)
1035  if db_execute_immediate(driver, byref(stmt)) != DB_OK:
1037  return -1
1038 
1040  G_free(poFi)
1041 
1042  if Vect_cat_set(poCatsTo, catsFrom.field[i], cat) < 1:
1043  continue
1044 
1045  if Vect_rewrite_line(self.poMapInfo, tline, ltype, self.poPoints, poCatsTo) < 0:
1046  self._error.WriteLine()
1047  return -1
1048 
1049  nlines +=1
1050 
1051  Vect_destroy_cats_struct(poCatsTo)
1052 
1053  if nlines > 0:
1054  self.toolbar.EnableUndo()
1055 
1056  return nlines
1057 
1058  def _selectLinesByQueryThresh(self):
1059  """!Generic method used for SelectLinesByQuery() -- to get
1060  threshold value"""
1061  thresh = 0.0
1062  if UserSettings.Get(group = 'vdigit', key = 'query', subkey = 'selection') == 0:
1063  thresh = UserSettings.Get(group = 'vdigit', key = 'queryLength', subkey = 'thresh')
1064  if UserSettings.Get(group = 'vdigit', key = "queryLength", subkey = 'than-selection') == 0:
1065  thresh = -1 * thresh
1066  else:
1067  thresh = UserSettings.Get(group = 'vdigit', key = 'queryDangle', subkey = 'thresh')
1068  if UserSettings.Get(group = 'vdigit', key = "queryDangle", subkey = 'than-selection') == 0:
1069  thresh = -1 * thresh
1070 
1071  return thresh
1072 
1073  def SelectLinesByQuery(self, bbox):
1074  """!Select features by query
1075 
1076  @todo layer / 3D
1077 
1078  @param bbox bounding box definition
1079  """
1080  if not self._checkMap():
1081  return -1
1082 
1083  thresh = self._selectLinesByQueryThresh()
1084 
1085  query = QUERY_UNKNOWN
1086  if UserSettings.Get(group = 'vdigit', key = 'query', subkey = 'selection') == 0:
1087  query = QUERY_LENGTH
1088  else:
1089  query = QUERY_DANGLE
1090 
1091  ftype = GV_POINTS | GV_LINES # TODO: 3D
1092  layer = 1 # TODO
1093 
1094  ids = list()
1095  poList = Vect_new_list()
1096  coList = poList.contents
1097  if UserSettings.Get(group = 'vdigit', key = 'query', subkey = 'box'):
1099  x1, y1 = bbox[0]
1100  x2, y2 = bbox[1]
1101  z1 = z2 = 0.0
1102 
1103  Vect_append_point(self.poPoints, x1, y1, z1)
1104  Vect_append_point(self.poPoints, x2, y1, z2)
1105  Vect_append_point(self.poPoints, x2, y2, z1)
1106  Vect_append_point(self.poPoints, x1, y2, z2)
1107  Vect_append_point(self.poPoints, x1, y1, z1)
1108 
1109  Vect_select_lines_by_polygon(self.poMapInfo, self.poPoints, 0, None,
1110  ftype, poList)
1111 
1112  if coList.n_values == 0:
1113  return ids
1114 
1116  ftype, layer, thresh, query,
1117  poList)
1118 
1119  for i in range(coList.n_values):
1120  ids.append(int(coList.value[i]))
1121 
1122  Debug.msg(3, "IVDigit.SelectLinesByQuery(): lines=%d", coList.n_values)
1123  Vect_destroy_list(poList)
1124 
1125  return ids
1126 
1127  def IsVector3D(self):
1128  """!Check if open vector map is 3D
1129  """
1130  if not self._checkMap():
1131  return False
1132 
1133  return Vect_is_3d(self.poMapInfo)
1134 
1135  def GetLineLength(self, line):
1136  """!Get line length
1137 
1138  @param line feature id
1139 
1140  @return line length
1141  @return -1 on error
1142  """
1143  if not self._checkMap():
1144  return -1
1145 
1146  if not Vect_line_alive(self.poMapInfo, line):
1147  return -1
1148 
1149  ltype = Vect_read_line(self.poMapInfo, self.poPoints, None, line)
1150  if ltype < 0:
1151  self._error.ReadLine(line)
1152  return ret
1153 
1154  length = -1
1155  if ltype & GV_LINES: # lines & boundaries
1156  length = Vect_line_length(self.poPoints)
1157 
1158  return length
1159 
1160  def GetAreaSize(self, centroid):
1161  """!Get area size
1162 
1163  @param centroid centroid id
1164 
1165  @return area size
1166  @return -1 on error
1167  """
1168  if not self._checkMap():
1169  return -1
1170 
1171  ltype = Vect_read_line(self.poMapInfo, None, None, centroid)
1172  if ltype < 0:
1173  self._error.ReadLine(line)
1174  return ret
1175 
1176  if ltype != GV_CENTROID:
1177  return -1
1178 
1179  area = Vect_get_centroid_area(self.poMapInfo, centroid)
1180  size = -1
1181  if area > 0:
1182  if not Vect_area_alive(self.poMapInfo, area):
1183  return size
1184 
1185  size = Vect_get_area_area(self.poMapInfo, area)
1186 
1187  return size
1188 
1189  def GetAreaPerimeter(self, centroid):
1190  """!Get area perimeter
1191 
1192  @param centroid centroid id
1193 
1194  @return area size
1195  @return -1 on error
1196  """
1197  if not self._checkMap():
1198  return -1
1199 
1200  ltype = Vect_read_line(self.poMapInfo, None, None, centroid)
1201  if ltype < 0:
1202  self._error.ReadLine(line)
1203  return ret
1204 
1205  if ltype != GV_CENTROID:
1206  return -1
1207 
1208  area = Vect_get_centroid_area(self.poMapInfo, centroid)
1209  perimeter = -1
1210  if area > 0:
1211  if not Vect_area_alive(self.poMapInfo, area):
1212  return -1
1213 
1214  Vect_get_area_points(self.poMapInfo, area, self.poPoints)
1215  perimeter = Vect_area_perimeter(self.poPoints)
1216 
1217  return perimeter
1218 
1219  def SetLineCats(self, line, layer, cats, add = True):
1220  """!Set categories for given line and layer
1221 
1222  @param line feature id
1223  @param layer layer number (-1 for first selected line)
1224  @param cats list of categories
1225  @param add if True to add, otherwise do delete categories
1226 
1227  @return new feature id (feature need to be rewritten)
1228  @return -1 on error
1229  """
1230  if not self._checkMap():
1231  return -1
1232 
1233  if line < 1 and len(self._display.selected['ids']) < 1:
1234  return -1
1235 
1236  update = False
1237  if line == -1:
1238  update = True
1239  line = self._display.selected['ids'][0]
1240 
1241  if not Vect_line_alive(self.poMapInfo, line):
1242  return -1
1243 
1244  ltype = Vect_read_line(self.poMapInfo, self.poPoints, self.poCats, line)
1245  if ltype < 0:
1246  self._error.ReadLine(line)
1247  return -1
1248 
1249  for c in cats:
1250  if add:
1251  Vect_cat_set(self.poCats, layer, c)
1252  else:
1253  Vect_field_cat_del(self.poCats, layer, c)
1254 
1255  nlines = Vect_get_num_lines(self.poMapInfo)
1256  changeset = self._addActionsBefore()
1257  newline = Vect_rewrite_line(self.poMapInfo, line, ltype,
1258  self.poPoints, self.poCats)
1259 
1260  if newline > 0:
1261  self._addActionsAfter(changeset, nlines)
1262  self.toolbar.EnableUndo()
1263 
1264  if update:
1265  # update line id since the line was rewritten
1266  self._display.selected['ids'][0] = newline
1267 
1268  return newline
1269 
1271  """!Feature type conversion for selected objects.
1272 
1273  Supported conversions:
1274  - point <-> centroid
1275  - line <-> boundary
1276 
1277  @return number of modified features
1278  @return -1 on error
1279  """
1280  if not self._checkMap():
1281  return -1
1282 
1283  nlines = Vect_get_num_lines(self.poMapInfo)
1284 
1285  # register changeset
1286  changeset = self._addActionsBefore()
1287 
1288  poList = self._display.GetSelectedIList()
1289  ret = Vedit_chtype_lines(self.poMapInfo, poList)
1290  Vect_destroy_list(poList)
1291 
1292  if ret > 0:
1293  self._addActionsAfter(changeset, nlines)
1294  self.toolbar.EnableUndo()
1295  else:
1296  del self.changesets[changeset]
1297 
1298  return ret
1299 
1300  def Undo(self, level = -1):
1301  """!Undo action
1302 
1303  @param level levels to undo (0 to revert all)
1304 
1305  @return id of current changeset
1306  """
1307  changesetLast = len(self.changesets.keys()) - 1
1308 
1309  if changesetLast < 0:
1310  return changesetLast
1311 
1312  if self.changesetCurrent == -2: # value uninitialized
1313  self.changesetCurrent = changesetLast
1314 
1315  if level > 0 and self.changesetCurrent < 0:
1316  self.changesetCurrent = 0
1317 
1318  if level == 0:
1319  # 0 -> undo all
1320  level = -1 * changesetLast + 1
1321 
1322  Debug.msg(2, "Digit.Undo(): changeset_last=%d, changeset_current=%d, level=%d",
1323  changesetLast, self.changesetCurrent, level)
1324 
1325  if level < 0: # undo
1326  if self.changesetCurrent + level < -1:
1327  return changesetCurrent;
1328  for changeset in range(self.changesetCurrent, self.changesetCurrent + level, -1):
1329  self._applyChangeset(changeset, undo = True)
1330  elif level > 0: # redo
1331  if self.changesetCurrent + level > len(self.changesets.keys()):
1332  return self.changesetCurrent
1333  for changeset in range(self.changesetCurrent, self.changesetCurrent + level):
1334  self._applyChangeset(changeset, undo = False)
1335 
1336  self.changesetCurrent += level
1337 
1338  Debug.msg(2, "Digit.Undo(): changeset_current=%d, changeset_last=%d, changeset_end=%d",
1339  self.changesetCurrent, changesetLast, self.changesetEnd)
1340 
1341  if self.changesetCurrent == self.changesetEnd:
1342  self.changesetEnd = changesetLast
1343  return -1
1344 
1345  self.mapWindow.UpdateMap(render = False)
1346 
1347  if self.changesetCurrent < 0: # disable undo tool
1348  self.toolbar.EnableUndo(False)
1349 
1350  def ZBulkLines(self, pos1, pos2, start, step):
1351  """!Z-bulk labeling
1352 
1353  @param pos1 reference line (start point)
1354  @param pos1 reference line (end point)
1355  @param start starting value
1356  @param step step value
1357 
1358  @return number of modified lines
1359  @return -1 on error
1360  """
1361  if not self._checkMap():
1362  return -1
1363 
1364  nlines = Vect_get_num_lines(self.poMapInfo)
1365 
1366  # register changeset
1367  changeset = self._addActionsBefore()
1368 
1369  poList = self._display.GetSelectedIList()
1370  ret = Vedit_bulk_labeling(self.poMapInfo, poList,
1371  pos1[0], pos1[1], pos2[0], pos2[1],
1372  start, step)
1373  Vect_destroy_list(poList)
1374 
1375  if ret > 0:
1376  self._addActionsAfter(changeset, nlines)
1377  self.toolbar.EnableUndo()
1378  else:
1379  del self.changesets[changeset]
1380 
1381  return ret
1382 
1383  def GetDisplay(self):
1384  """!Get display driver instance"""
1385  return self._display
1386 
1387  def OpenMap(self, name):
1388  """!Open vector map for editing
1389 
1390  @param map name of vector map to be set up
1391  """
1392  Debug.msg (3, "AbstractDigit.SetMapName map=%s" % name)
1393 
1394  name, mapset = name.split('@')
1395 
1396  self.poMapInfo = self._display.OpenMap(str(name), str(mapset), True)
1397 
1398  if self.poMapInfo:
1399  self.InitCats()
1400 
1401  return self.poMapInfo
1402 
1403  def CloseMap(self):
1404  """!Close currently open vector map
1405  """
1406  if not self._checkMap():
1407  return
1408 
1409  self._display.CloseMap()
1410 
1411  def InitCats(self):
1412  """!Initialize categories information
1413 
1414  @return 0 on success
1415  @return -1 on error
1416  """
1417  self.cats.clear()
1418  if not self._checkMap():
1419  return -1
1420 
1421  ndblinks = Vect_get_num_dblinks(self.poMapInfo)
1422  for i in range(ndblinks):
1423  fi = Vect_get_dblink(self.poMapInfo, i).contents
1424  if fi:
1425  self.cats[fi.number] = None
1426 
1427  # find max category
1428  nfields = Vect_cidx_get_num_fields(self.poMapInfo)
1429  Debug.msg(2, "wxDigit.InitCats(): nfields=%d", nfields)
1430 
1431  for i in range(nfields):
1432  field = Vect_cidx_get_field_number(self.poMapInfo, i)
1434  if field <= 0:
1435  continue
1436  for j in range(ncats):
1437  cat = c_int()
1438  type = c_int()
1439  id = c_int()
1441  byref(cat), byref(type), byref(id))
1442  if field in self.cats:
1443  if cat > self.cats[field]:
1444  self.cats[field] = cat.value
1445  else:
1446  self.cats[field] = cat.value
1447  Debug.msg(3, "wxDigit.InitCats(): layer=%d, cat=%d", field, self.cats[field])
1448 
1449  # set default values
1450  for field, cat in self.cats.iteritems():
1451  if cat == None:
1452  self.cats[field] = 0 # first category 1
1453  Debug.msg(3, "wxDigit.InitCats(): layer=%d, cat=%d", field, self.cats[field])
1454 
1455  def _checkMap(self):
1456  """!Check if map is open
1457  """
1458  if not self.poMapInfo:
1459  self._error.NoMap()
1460  return False
1461 
1462  return True
1463 
1464  def _addFeature(self, ftype, coords, layer, cat, snap, threshold):
1465  """!Add new feature(s) to the vector map
1466 
1467  @param ftype feature type (GV_POINT, GV_LINE, GV_BOUNDARY, ...)
1468  @coords tuple of coordinates ((x, y), (x, y), ...)
1469  @param layer layer number (-1 for no cat)
1470  @param cat category number
1471  @param snap snap to node/vertex
1472  @param threshold threshold for snapping
1473 
1474  @return tuple (number of added features, list of fids)
1475  @return number of features -1 on error
1476  """
1477  fids = list()
1478  if not self._checkMap():
1479  return (-1, None)
1480 
1481  is3D = bool(Vect_is_3d(self.poMapInfo))
1482 
1483  Debug.msg(2, "IVDigit._addFeature(): npoints=%d, layer=%d, cat=%d, snap=%d",
1484  len(coords), layer, cat, snap)
1485 
1486  if not (ftype & (GV_POINTS | GV_LINES | GV_AREA)): # TODO: 3D
1487  self._error.FeatureType(ftype)
1488  return (-1, None)
1489 
1490  # set category
1491  Vect_reset_cats(self.poCats)
1492  if layer > 0 and ftype != GV_AREA:
1493  Vect_cat_set(self.poCats, layer, cat)
1494  self.cats[layer] = max(cat, self.cats.get(layer, 1))
1495 
1496  # append points
1498  for c in coords:
1499  Vect_append_point(self.poPoints, c[0], c[1], 0.0)
1500 
1501  if ftype & (GV_BOUNDARY | GV_AREA):
1502  # close boundary
1503  points = self.poPoints.contents
1504  last = points.n_points - 1
1505  if Vect_points_distance(points.x[0], points.x[0], points.z[0],
1506  points.x[last], points.x[last], points.z[last],
1507  is3D) <= threshold:
1508  points.x[last] = points.x[0]
1509  points.y[last] = points.y[0]
1510  points.z[last] = points.z[0]
1511 
1512  if snap != NO_SNAP:
1513  # apply snapping (node or vertex)
1514  modeSnap = not (snap == SNAP)
1515  Vedit_snap_line(self.poMapInfo, self.popoBgMapInfo, int(self.poBgMapInfo is not None),
1516  -1, self.poPoints, threshold, modeSnap)
1517 
1518  if ftype == GV_AREA:
1519  ltype = GV_BOUNDARY
1520  else:
1521  ltype = ftype
1522  newline = Vect_write_line(self.poMapInfo, ltype, self.poPoints, self.poCats)
1523  if newline < 0:
1524  self._error.WriteLine()
1525  return (-1, None)
1526  else:
1527  fids.append(newline)
1528 
1529  left = right = -1
1530  if ftype & GV_AREA:
1531  # add centroids for left/right area
1532  bpoints = Vect_new_line_struct()
1533  cleft = c_int()
1534  cright = c_int()
1535 
1536  Vect_get_line_areas(self.poMapInfo, newline,
1537  byref(cleft), byref(cright))
1538  left = cleft.value
1539  right = cright.value
1540 
1541  Debug.msg(3, "IVDigit._addFeature(): area - left=%d right=%d",
1542  left, right)
1543 
1544  # check if area exists and has no centroid inside
1545  if layer > 0 and (left > 0 or right > 0):
1546  Vect_cat_set(self.poCats, layer, cat)
1547  self.cats[layer] = max(cat, self.cats.get(layer, 0))
1548 
1549  x = c_double()
1550  y = c_double()
1551  if left > 0 and \
1552  Vect_get_area_centroid(self.poMapInfo, left) == 0:
1553  # if Vect_get_area_points(self.poMapInfo, left, bpoints) > 0 and
1554  # Vect_find_poly_centroid(bpoints, byref(x), byref(y)) == 0:
1555  if Vect_get_point_in_area(self.poMapInfo, left, byref(x), byref(y)) == 0:
1556  Vect_reset_line(bpoints)
1557  Vect_append_point(bpoints, x.value, y.value, 0.0)
1558  newline = Vect_write_line(self.poMapInfo, GV_CENTROID,
1559  bpoints, self.poCats)
1560  if newline < 0:
1561  self._error.WriteLine()
1562  return (len(fids), fids)
1563  else:
1564  fids.append(newline)
1565 
1566  if right > 0 and \
1567  Vect_get_area_centroid(self.poMapInfo, right) == 0:
1568  # if Vect_get_area_points(byref(self.poMapInfo), right, bpoints) > 0 and
1569  # Vect_find_poly_centroid(bpoints, byref(x), byref(y)) == 0:
1570  if Vect_get_point_in_area(self.poMapInfo, right, byref(x), byref(y)) == 0:
1571  Vect_reset_line(bpoints)
1572  Vect_append_point(bpoints, x.value, y.value, 0.0)
1573  newline = Vect_write_line(self.poMapInfo, GV_CENTROID,
1574  bpoints, self.poCats)
1575  if newline < 0:
1576  self._error.WriteLine()
1577  return (len(fids, fids))
1578  else:
1579  fids.append(newline)
1580 
1581  Vect_destroy_line_struct(bpoints)
1582 
1583  # register changeset
1584  changeset = len(self.changesets)
1585  self._addActionToChangeset(changeset, newline, add = True)
1586 
1587  # break at intersection
1588  if self._settings['breakLines']:
1589  self._breakLineAtIntersection(newline, self.poPoints, changeset)
1590 
1591  return (len(fids), fids)
1592 
1593  def _ModifyLineVertex(self, coords, add = True):
1594  """!Add or remove vertex
1595 
1596  Shape of line/boundary is not changed when adding new vertex.
1597 
1598  @param coords coordinates of point
1599  @param add True to add, False to remove
1600 
1601  @return id id of the new feature
1602  @return 0 nothing changed
1603  @return -1 error
1604  """
1605  if not self._checkMap():
1606  return -1
1607 
1608  selected = self._display.selected
1609  if len(selected['ids']) != 1:
1610  return 0
1611 
1612  poList = self._display.GetSelectedIList()
1614  Vect_append_point(self.poPoints, coords[0], coords[1], 0.0)
1615 
1616  nlines = Vect_get_num_lines(self.poMapInfo)
1617  thresh = self._display.GetThreshold(type = 'selectThresh')
1618 
1619  changeset = self._addActionsBefore()
1620 
1621  if add:
1622  ret = Vedit_add_vertex(self.poMapInfo, poList,
1623  self.poPoints, thresh)
1624  else:
1625  ret = Vedit_remove_vertex(self.poMapInfo, poList,
1626  self.poPoints, thresh)
1627  Vect_destroy_list(poList)
1628 
1629  if ret > 0:
1630  self._addActionsAfter(changeset, nlines)
1631  else:
1632  del self.changesets[changeset]
1633 
1634  if not add and ret > 0 and self._settings['breakLines']:
1636  None, changeset)
1637 
1638  return nlines + 1 # feature is write at the end of the file
1639 
1640  def GetLineCats(self, line):
1641  """!Get list of layer/category(ies) for selected feature.
1642 
1643  @param line feature id (-1 for first selected feature)
1644 
1645  @return list of layer/cats
1646  """
1647  ret = dict()
1648  if not self._checkMap():
1649  return ret
1650 
1651  if line == -1 and len(self._display.selected['ids']) < 1:
1652  return ret
1653 
1654  if line == -1:
1655  line = self._display.selected['ids'][0]
1656 
1657  if not Vect_line_alive(self.poMapInfo, line):
1658  self._error.DeadLine(line)
1659  return ret
1660 
1661  if Vect_read_line(self.poMapInfo, None, self.poCats, line) < 0:
1662  self._error.ReadLine(line)
1663  return ret
1664 
1665  cats = self.poCats.contents
1666  for i in range(cats.n_cats):
1667  field = cats.field[i]
1668  if field not in ret:
1669  ret[field] = list()
1670  ret[field].append(cats.cat[i])
1671 
1672  return ret
1673 
1674  def GetLayers(self):
1675  """!Get list of layers
1676 
1677  Requires self.InitCats() to be called.
1678 
1679  @return list of layers
1680  """
1681  return self.cats.keys()
1682 
1683  def UpdateSettings(self):
1684  """!Update digit (and display) settings
1685  """
1686  self._display.UpdateSettings()
1687 
1688  self._settings['breakLines'] = bool(UserSettings.Get(group = 'vdigit', key = "breakLines",
1689  subkey = 'enabled'))
1690 
1691  def SetCategory(self):
1692  """!Update self.cats based on settings"""
1693  sel = UserSettings.Get(group = 'vdigit', key = 'categoryMode', subkey = 'selection')
1694  cat = None
1695  if sel == 0: # next to usep
1696  cat = self._setCategoryNextToUse()
1697  elif sel == 1:
1698  cat = UserSettings.Get(group = 'vdigit', key = 'category', subkey = 'value')
1699 
1700  if cat:
1701  layer = UserSettings.Get(group = 'vdigit', key = 'layer', subkey = 'value')
1702  self.cats[layer] = cat
1703 
1704  return cat
1705 
1706  def _setCategoryNextToUse(self):
1707  """!Find maximum category number for the given layer and
1708  update the settings
1709 
1710  @return category to be used
1711  """
1712  # get max category number for given layer and update the settings
1713  layer = UserSettings.Get(group = 'vdigit', key = 'layer', subkey = 'value')
1714  cat = self.cats.get(layer, 0) + 1
1715  UserSettings.Set(group = 'vdigit', key = 'category', subkey = 'value',
1716  value = cat)
1717  Debug.msg(1, "IVDigit._setCategoryNextToUse(): cat=%d", cat)
1718 
1719  return cat
1720 
1722  """!Select features from background map
1723 
1724  @param bbox bounding box definition
1725 
1726  @return list of selected feature ids
1727  """
1728  # try select features by box first
1729  if self._display.SelectLinesByBox(bbox, poMapInfo = self.poBgMapInfo) < 1:
1730  self._display.SelectLineByPoint(bbox[0], poMapInfo = self.poBgMapInfo)['line']
1731 
1732  return self._display.selected['ids']
1733 
1734  def GetUndoLevel(self):
1735  """!Get undo level (number of active changesets)
1736 
1737  Note: Changesets starts wiht 0
1738  """
1739  return self.changesetCurrent
1740