2 @package vdigit.wxdigit
4 @brief wxGUI vector digitizer (base class)
6 Code based on wxVdigit C++ component from GRASS 6.4.0
7 (gui/wxpython/vdigit). Converted to Python in 2010/12-2011/01.
10 - wxdigit::VDigitError
13 @todo Read large amounts of data from Vlib into arrays, which could
14 then be processed using NumPy and rendered using glDrawArrays or
15 glDrawElements, so no per-line/per-vertex processing in Python. Bulk
16 data processing with NumPy is much faster than iterating in Python
17 (and NumPy would be an excellent candidate for acceleration via
18 e.g. OpenCL or CUDA; I'm surprised it hasn't happened already).
20 (C) 2007-2011 by the GRASS Development Team
22 This program is free software under the GNU General Public License
23 (>=v2). Read the file COPYING that comes with GRASS for details.
25 @author Martin Landa <landa.martin gmail.com>
45 """!Class for managing error messages of vector digitizer
47 @param parent parent window for dialogs
53 """!No map for editing"""
55 message = _(
'Unable to open vector map <%s>.') % name
57 message = _(
'No vector map open for editing.')
58 GError(message +
' ' + _(
'Operation canceled.'),
63 """!Writing line failed
65 GError(message = _(
'Writing new feature failed. '
66 'Operation cancelled.'),
71 """!Reading line failed
73 GError(message = _(
'Reading feature id %d failed. '
74 'Operation canceled.') % line,
79 """!No dblink available
81 GError(message = _(
'Database link %d not available. '
82 'Operation canceled.') % dblink,
87 """!Staring driver failed
89 GError(message = _(
'Unable to start database driver <%s>. '
90 'Operation canceled.') % driver,
95 """!Opening database failed
97 GError(message = _(
'Unable to open database <%(db)s> by driver <%(driver)s>. '
98 'Operation canceled.') % {
'db' : database,
'driver' : driver},
105 GError(message = _(
"Unable to execute SQL query '%s'. "
106 "Operation canceled.") % sql,
113 GError(message = _(
"Feature id %d is marked as dead. "
114 "Operation canceled.") % line,
119 """!Unknown feature type
121 GError(message = _(
"Unsupported feature type %d. "
122 "Operation canceled.") % ftype,
128 """!Base class for vector digitizer (ctypes interface)
130 @parem mapwindow reference for map window (BufferedWindow)
139 if not mapwindow.parent.IsStandalone():
140 goutput = mapwindow.parent.GetLayerManager().GetLogWindow()
141 log = goutput.GetLog(err =
True)
142 progress = goutput.GetProgressBar()
147 self.
toolbar = mapwindow.parent.toolbars[
'vdigit']
151 self.
_display = DisplayDriver(device = mapwindow.pdcVector,
152 deviceTmp = mapwindow.pdcTmp,
153 mapObj = mapwindow.Map,
156 gprogress = progress)
179 Debug.msg(1,
"IVDigit.__del__()")
191 """!Close background vector map"""
199 """!Open background vector map
201 @todo support more background maps then only one
203 @param bgmap name of vector map to be opened
205 @return pointer to map_info
206 @return None on error
208 name = create_string_buffer(GNAME_MAX)
209 mapset = create_string_buffer(GMAPSET_MAX)
214 name = str(name.value)
215 mapset = str(mapset.value)
220 self._error.NoMap(bgmap)
227 self._error.NoMap(bgmap)
230 def _getSnapMode(self):
231 """!Get snapping mode
239 threshold = self._display.GetThreshold()
241 if UserSettings.Get(group =
'vdigit', key =
'snapToVertex', subkey =
'enabled'):
248 def _breakLineAtIntersection(self, line, pointsLine, changeset):
249 """!Break given line at intersection
252 \param pointsLine line geometry
255 \return number of modified lines
265 self._error.ReadLine(line)
277 lineBox = bound_box()
286 for i
in range(listLine.contents.n_values):
287 lineBreak = listLine.contents.value[i]
288 if lineBreak == line:
292 if not (ltype & GV_LINES):
301 for i
in range(listBreak.contents.n_values):
307 for i
in range(listBreak.contents.n_values):
323 def _addActionsBefore(self):
324 """!Register action before operation
329 for line
in self._display.selected[
'ids']:
335 def _applyChangeset(self, changeset, undo):
336 """!Apply changeset (undo/redo changeset)
338 @param changeset changeset id
339 @param undo True for undo otherwise redo
341 @return 1 changeset applied
342 @return 0 changeset not applied
345 if changeset < 0
or changeset > len(self.changesets.keys()):
353 for action
in actions:
355 line = action[
'line']
356 if (undo
and add)
or \
357 (
not undo
and not add):
359 Debug.msg(3,
"IVDigit._applyChangeset(): changeset=%d, action=add, line=%d -> deleted",
364 Debug.msg(3,
"Digit.ApplyChangeset(): changeset=%d, action=add, line=%d dead",
367 offset = action[
'offset']
369 Debug.msg(3,
"Digit.ApplyChangeset(): changeset=%d, action=delete, line=%d -> added",
375 Debug.msg(3,
"Digit.ApplyChangeset(): changeset=%d, action=delete, line=%d alive",
380 def _addActionsAfter(self, changeset, nlines):
381 """!Register action after operation
383 @param changeset changeset id
384 @param nline number of lines
386 for line
in self._display.selected[
'ids']:
394 def _addActionToChangeset(self, changeset, line, add):
395 """!Add action to changeset
397 @param changeset id of changeset
398 @param line feature id
399 @param add True to add, otherwise delete
413 self.
changesets[changeset].append({
'add' : add,
417 Debug.msg(3,
"IVDigit._addActionToChangeset(): changeset=%d, add=%d, line=%d, offset=%d",
418 changeset, add, line, offset)
420 def _removeActionFromChangeset(self, changeset, line, add):
421 """!Remove action from changeset
423 @param changeset changeset id
425 @param add True for add, False for delete
427 @return number of actions in changeset
430 if changeset
not in self.changesets.keys():
435 if action[
'add'] == add
and action[
'line'] == line:
443 @param ftype feature type (point, line, centroid, boundary)
444 @param points tuple of points ((x, y), (x, y), ...)
446 @return tuple (number of added features, feature ids)
448 if UserSettings.Get(group =
'vdigit', key =
"categoryMode", subkey =
'selection') == 2:
452 layer = UserSettings.Get(group =
'vdigit', key =
"layer", subkey =
'value')
457 elif ftype ==
'line':
459 elif ftype ==
'centroid':
461 elif ftype ==
'boundary':
463 elif ftype ==
'area':
467 message = _(
"Unknown feature type '%s'") % ftype)
470 if vtype & GV_LINES
and len(points) < 2:
472 message = _(
"Not enough points for line"))
475 self.toolbar.EnableUndo()
481 """!Delete selected features
483 @return number of deleted features
485 deleteRec = UserSettings.Get(group =
'vdigit', key =
'delRecord', subkey =
'enabled')
495 for i
in self._display.selected[
'ids']:
498 self._error.ReadLine(i)
501 cats = self.poCats.contents
502 for j
in range(cats.n_cats):
507 poList = self._display.GetSelectedIList()
511 self._display.selected[
'ids'] = list()
513 if nlines > 0
and deleteRec:
515 poHandle = pointer(handle)
517 poStmt = pointer(stmt)
519 for dblink
in range(n_dblinks):
522 self._error.DbLink(dblink)
528 self._error.Driver(Fi.driver)
534 self._error.Database(Fi.driver, Fi.database)
540 catsDel = poCatsDel.contents
541 for c
in range(catsDel.n_cats):
542 if catsDel.field[c] == Fi.number:
563 self.toolbar.EnableUndo()
568 """!Move selected features
570 @param move direction (x, y)
575 thresh = self._display.GetThreshold()
583 poList = self._display.GetSelectedIList()
595 if nlines > 0
and self.
_settings[
'breakLines']:
596 for i
in range(1, nlines):
600 self.toolbar.EnableUndo()
605 """!Move selected vertex of the line
607 @param point location point
608 @param move x,y direction
610 @return id of new feature
611 @return 0 vertex not moved (not found, line is not selected)
617 if len(self._display.selected[
'ids']) != 1:
628 poList = self._display.GetSelectedIList()
631 self._display.GetThreshold(type =
'selectThresh'),
632 self._display.GetThreshold(),
633 move[0], move[1], 0.0,
642 if moved > 0
and self.
_settings[
'breakLines']:
647 self.toolbar.EnableUndo()
652 """!Add new vertex to the selected line/boundary on position 'coords'
654 @param coords coordinates to add vertex
656 @return id of new feature
657 @return 0 nothing changed
658 @return -1 on failure
663 self.toolbar.EnableUndo()
668 """!Remove vertex from the selected line/boundary on position 'coords'
670 @param coords coordinates to remove vertex
672 @return id of new feature
673 @return 0 nothing changed
674 @return -1 on failure
679 self.toolbar.EnableUndo()
685 """!Split/break selected line/boundary on given position
687 @param point point where to split line
689 @return 1 line modified
690 @return 0 nothing changed
693 thresh = self._display.GetThreshold(
'selectThresh')
697 poList = self._display.GetSelectedIList()
711 self.toolbar.EnableUndo()
718 """!Edit existing line/boundary
720 @param line feature id to be modified
721 @param coords list of coordinates of modified line
723 @return feature id of new line
734 self._error.DeadLine(line)
740 self._error.ReadLine(line)
751 modeSnap =
not (snap == SNAP)
754 -1, self.
poPoints, self._display.GetThreshold(), modeSnap)
763 self.toolbar.EnableUndo()
767 if newline > 0
and self.
_settings[
'breakLines']:
773 """!Flip selected lines/boundaries
775 @return number of modified lines
786 poList = self._display.GetSelectedIList()
792 self.toolbar.EnableUndo()
799 """!Merge selected lines/boundaries
801 @return number of modified lines
811 poList = self._display.GetSelectedIList()
817 self.toolbar.EnableUndo()
824 """!Break selected lines/boundaries
826 @return number of modified lines
836 poList = self._display.GetSelectedIList()
843 self.toolbar.EnableUndo()
850 """!Snap selected lines/boundaries
862 poList = self._display.GetSelectedIList()
864 self._display.GetThreshold(),
None)
869 self.toolbar.EnableUndo()
874 """!Connect selected lines/boundaries
876 @return 1 lines connected
877 @return 0 lines not connected
888 poList = self._display.GetSelectedIList()
890 self._display.GetThreshold())
895 self.toolbar.EnableUndo()
902 """!Copy features from (background) vector map
904 @param ids list of line ids to be copied
906 @return number of copied features
914 poList = self._display.GetSelectedIList(ids)
923 self.toolbar.EnableUndo()
928 for i
in range(1, ret):
933 def CopyCats(self, fromId, toId, copyAttrb = False):
934 """!Copy given categories to objects with id listed in ids
936 @param cats ids of 'from' feature
937 @param ids ids of 'to' feature(s)
939 @return number of modified features
942 if len(fromId) < 1
or len(toId) < 1:
955 self._error.ReadLine(fline)
964 self._error.ReadLine(fline)
967 catsFrom = poCatsFrom.contents
968 for i
in range(catsFrom.n_cats):
971 cat = catsFrom.cat[i]
974 cat = self.
cats[catsFrom.field[i]] + 1
975 self.
cats[catsFrom.field[i]] = cat
978 self._error.DbLink(i)
984 self._error.Driver(fi.driver)
992 self._error.Database(fi.driver, fi.database)
998 "SELECT * FROM %s WHERE %s=%d" % (fi.table, fi.key,
1003 DB_SEQUENTIAL) != DB_OK:
1010 sql =
"INSERT INTO %s VALUES (" % fi.table
1014 if db_fetch(byref(cursor), DB_NEXT, byref(more)) != DB_OK:
1020 value_string = dbString()
1021 for col
in range(ncols):
1036 if ctype != DB_C_TYPE_STRING:
1054 self._error.WriteLine()
1062 self.toolbar.EnableUndo()
1066 def _selectLinesByQueryThresh(self):
1067 """!Generic method used for SelectLinesByQuery() -- to get
1070 if UserSettings.Get(group =
'vdigit', key =
'query', subkey =
'selection') == 0:
1071 thresh = UserSettings.Get(group =
'vdigit', key =
'queryLength', subkey =
'thresh')
1072 if UserSettings.Get(group =
'vdigit', key =
"queryLength", subkey =
'than-selection') == 0:
1073 thresh = -1 * thresh
1075 thresh = UserSettings.Get(group =
'vdigit', key =
'queryDangle', subkey =
'thresh')
1076 if UserSettings.Get(group =
'vdigit', key =
"queryDangle", subkey =
'than-selection') == 0:
1077 thresh = -1 * thresh
1082 """!Select features by query
1086 @param bbox bounding box definition
1093 query = QUERY_UNKNOWN
1094 if UserSettings.Get(group =
'vdigit', key =
'query', subkey =
'selection') == 0:
1095 query = QUERY_LENGTH
1097 query = QUERY_DANGLE
1099 ftype = GV_POINTS | GV_LINES
1104 coList = poList.contents
1105 if UserSettings.Get(group =
'vdigit', key =
'query', subkey =
'box'):
1120 if coList.n_values == 0:
1124 ftype, layer, thresh, query,
1127 for i
in range(coList.n_values):
1128 ids.append(int(coList.value[i]))
1130 Debug.msg(3,
"IVDigit.SelectLinesByQuery(): lines=%d", coList.n_values)
1136 """!Check if open vector map is 3D
1146 @param line feature id
1159 self._error.ReadLine(line)
1163 if ltype & GV_LINES:
1171 @param centroid centroid id
1181 self._error.ReadLine(line)
1184 if ltype != GV_CENTROID:
1198 """!Get area perimeter
1200 @param centroid centroid id
1210 self._error.ReadLine(line)
1213 if ltype != GV_CENTROID:
1228 """!Set categories for given line and layer
1230 @param line feature id
1231 @param layer layer number (-1 for first selected line)
1232 @param cats list of categories
1233 @param add if True to add, otherwise do delete categories
1235 @return new feature id (feature need to be rewritten)
1241 if line < 1
and len(self._display.selected[
'ids']) < 1:
1247 line = self._display.selected[
'ids'][0]
1254 self._error.ReadLine(line)
1270 self.toolbar.EnableUndo()
1274 self._display.selected[
'ids'][0] = newline
1279 """!Feature type conversion for selected objects.
1281 Supported conversions:
1282 - point <-> centroid
1285 @return number of modified features
1296 poList = self._display.GetSelectedIList()
1302 self.toolbar.EnableUndo()
1311 @param level levels to undo (0 to revert all)
1313 @return id of current changeset
1315 changesetLast = len(self.changesets.keys()) - 1
1317 if changesetLast < 0:
1318 return changesetLast
1328 level = -1 * changesetLast + 1
1330 Debug.msg(2,
"Digit.Undo(): changeset_last=%d, changeset_current=%d, level=%d",
1335 return changesetCurrent;
1346 Debug.msg(2,
"Digit.Undo(): changeset_current=%d, changeset_last=%d, changeset_end=%d",
1353 self.mapWindow.UpdateMap(render =
False)
1356 self.toolbar.EnableUndo(
False)
1361 @param pos1 reference line (start point)
1362 @param pos1 reference line (end point)
1363 @param start starting value
1364 @param step step value
1366 @return number of modified lines
1377 poList = self._display.GetSelectedIList()
1379 pos1[0], pos1[1], pos2[0], pos2[1],
1385 self.toolbar.EnableUndo()
1392 """!Get display driver instance"""
1396 """!Open vector map for editing
1398 @param map name of vector map to be set up
1400 Debug.msg (3,
"AbstractDigit.SetMapName map=%s" % name)
1402 name, mapset = name.split(
'@')
1404 self.
poMapInfo = self._display.OpenMap(str(name), str(mapset),
True)
1412 """!Close currently open vector map
1417 self._display.CloseMap()
1420 """!Initialize categories information
1422 @return 0 on success
1430 for i
in range(ndblinks):
1433 self.
cats[fi.number] =
None
1437 Debug.msg(2,
"wxDigit.InitCats(): nfields=%d", nfields)
1439 for i
in range(nfields):
1444 for j
in range(ncats):
1449 byref(cat), byref(type), byref(id))
1450 if field
in self.
cats:
1451 if cat > self.
cats[field]:
1452 self.
cats[field] = cat.value
1454 self.
cats[field] = cat.value
1455 Debug.msg(3,
"wxDigit.InitCats(): layer=%d, cat=%d", field, self.
cats[field])
1458 for field, cat
in self.cats.iteritems():
1460 self.
cats[field] = 0
1461 Debug.msg(3,
"wxDigit.InitCats(): layer=%d, cat=%d", field, self.
cats[field])
1463 def _checkMap(self):
1464 """!Check if map is open
1472 def _addFeature(self, ftype, coords, layer, cat, snap, threshold):
1473 """!Add new feature(s) to the vector map
1475 @param ftype feature type (GV_POINT, GV_LINE, GV_BOUNDARY, ...)
1476 @coords tuple of coordinates ((x, y), (x, y), ...)
1477 @param layer layer number (-1 for no cat)
1478 @param cat category number
1479 @param snap snap to node/vertex
1480 @param threshold threshold for snapping
1482 @return tuple (number of added features, list of fids)
1483 @return number of features -1 on error
1491 Debug.msg(2,
"IVDigit._addFeature(): npoints=%d, layer=%d, cat=%d, snap=%d",
1492 len(coords), layer, cat, snap)
1494 if not (ftype & (GV_POINTS | GV_LINES | GV_AREA)):
1495 self._error.FeatureType(ftype)
1500 if layer > 0
and ftype != GV_AREA:
1502 self.
cats[layer] =
max(cat, self.cats.get(layer, 1))
1509 if ftype & (GV_BOUNDARY | GV_AREA):
1511 points = self.poPoints.contents
1512 last = points.n_points - 1
1514 points.x[last], points.y[last], points.z[last],
1516 points.x[last] = points.x[0]
1517 points.y[last] = points.y[0]
1518 points.z[last] = points.z[0]
1522 modeSnap =
not (snap == SNAP)
1524 -1, self.
poPoints, threshold, modeSnap)
1526 if ftype == GV_AREA:
1532 self._error.WriteLine()
1535 fids.append(newline)
1545 byref(cleft), byref(cright))
1547 right = cright.value
1549 Debug.msg(3,
"IVDigit._addFeature(): area - left=%d right=%d",
1553 if layer > 0
and (left > 0
or right > 0):
1555 self.
cats[layer] =
max(cat, self.cats.get(layer, 0))
1569 self._error.WriteLine()
1570 return (len(fids), fids)
1572 fids.append(newline)
1584 self._error.WriteLine()
1585 return (len(fids, fids))
1587 fids.append(newline)
1599 return (len(fids), fids)
1601 def _ModifyLineVertex(self, coords, add = True):
1602 """!Add or remove vertex
1604 Shape of line/boundary is not changed when adding new vertex.
1606 @param coords coordinates of point
1607 @param add True to add, False to remove
1609 @return id id of the new feature
1610 @return 0 nothing changed
1616 selected = self._display.selected
1617 if len(selected[
'ids']) != 1:
1620 poList = self._display.GetSelectedIList()
1625 thresh = self._display.GetThreshold(type =
'selectThresh')
1642 if not add
and ret > 0
and self.
_settings[
'breakLines']:
1649 """!Get list of layer/category(ies) for selected feature.
1651 @param line feature id (-1 for first selected feature)
1653 @return list of layer/cats
1659 if line == -1
and len(self._display.selected[
'ids']) < 1:
1663 line = self._display.selected[
'ids'][0]
1666 self._error.DeadLine(line)
1670 self._error.ReadLine(line)
1673 cats = self.poCats.contents
1674 for i
in range(cats.n_cats):
1675 field = cats.field[i]
1676 if field
not in ret:
1678 ret[field].append(cats.cat[i])
1683 """!Get list of layers
1685 Requires self.InitCats() to be called.
1687 @return list of layers
1689 return self.cats.keys()
1692 """!Update digit (and display) settings
1694 self._display.UpdateSettings()
1696 self.
_settings[
'breakLines'] = bool(UserSettings.Get(group =
'vdigit', key =
"breakLines",
1697 subkey =
'enabled'))
1700 """!Update self.cats based on settings"""
1701 sel = UserSettings.Get(group =
'vdigit', key =
'categoryMode', subkey =
'selection')
1706 cat = UserSettings.Get(group =
'vdigit', key =
'category', subkey =
'value')
1709 layer = UserSettings.Get(group =
'vdigit', key =
'layer', subkey =
'value')
1710 self.
cats[layer] = cat
1714 def _setCategoryNextToUse(self):
1715 """!Find maximum category number for the given layer and
1718 @return category to be used
1721 layer = UserSettings.Get(group =
'vdigit', key =
'layer', subkey =
'value')
1722 cat = self.cats.get(layer, 0) + 1
1723 UserSettings.Set(group =
'vdigit', key =
'category', subkey =
'value',
1725 Debug.msg(1,
"IVDigit._setCategoryNextToUse(): cat=%d", cat)
1730 """!Select features from background map
1732 @param bbox bounding box definition
1734 @return list of selected feature ids
1737 if self._display.SelectLinesByBox(bbox, poMapInfo = self.
poBgMapInfo) < 1:
1738 self._display.SelectLineByPoint(bbox[0], poMapInfo = self.
poBgMapInfo)[
'line']
1740 return self._display.selected[
'ids']
1743 """!Get undo level (number of active changesets)
1745 Note: Changesets starts wiht 0