123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178 |
- """
- @package vdigit.wxdigit
- @brief wxGUI vector digitizer (base class)
- Code based on wxVdigit C++ component from GRASS 6.4.0
- (gui/wxpython/vdigit). Converted to Python in 2010/12-2011/01.
- List of classes:
- - wxdigit::VDigitError
- - wxdigit::IVDigit
- .. todo::
- Read large amounts of data from Vlib into arrays, which could
- then be processed using NumPy and rendered using glDrawArrays or
- glDrawElements, so no per-line/per-vertex processing in Python. Bulk
- data processing with NumPy is much faster than iterating in Python
- (and NumPy would be an excellent candidate for acceleration via
- e.g. OpenCL or CUDA; I'm surprised it hasn't happened already).
- (C) 2007-2016 by the GRASS Development Team
- This program is free software under the GNU General Public License
- (>=v2). Read the file COPYING that comes with GRASS for details.
- @author Martin Landa <landa.martin gmail.com>
- """
- from __future__ import print_function
- import six
- import grass.script.core as grass
- from grass.pydispatch.signal import Signal
- from core.gcmd import GError
- from core.debug import Debug
- from core.settings import UserSettings
- from vdigit.wxdisplay import DisplayDriver, GetLastError
- try:
- WindowsError
- except NameError:
- WindowsError = OSError
- try:
- from grass.lib.gis import *
- from grass.lib.vector import *
- from grass.lib.vedit import *
- from grass.lib.dbmi import *
- except (ImportError, WindowsError, TypeError) as e:
- print("wxdigit.py: {}".format(e), file=sys.stderr)
- class VDigitError:
- def __init__(self, parent):
- """Class for managing error messages of vector digitizer
- :param parent: parent window for dialogs
- """
- self.parent = parent
- self.caption = _("Digitization Error")
- def NoMap(self, name=None):
- """No map for editing"""
- if name:
- message = _("Unable to open vector map <%s>.") % name
- else:
- message = _("No vector map open for editing.")
- GError(
- message + " " + _("Operation canceled."),
- parent=self.parent,
- caption=self.caption,
- )
- def WriteLine(self):
- """Writing line failed"""
- GError(
- message=_(
- "Writing new feature failed. " "Operation canceled.\n\n" "Reason: %s"
- )
- % GetLastError(),
- parent=self.parent,
- caption=self.caption,
- )
- def ReadLine(self, line):
- """Reading line failed"""
- GError(
- message=_("Reading feature id %d failed. " "Operation canceled.") % line,
- parent=self.parent,
- caption=self.caption,
- )
- def DbLink(self, dblink):
- """No dblink available"""
- GError(
- message=_("Database link %d not available. " "Operation canceled.")
- % dblink,
- parent=self.parent,
- caption=self.caption,
- )
- def Driver(self, driver):
- """Staring driver failed"""
- GError(
- message=_("Unable to start database driver <%s>. " "Operation canceled.")
- % driver,
- parent=self.parent,
- caption=self.caption,
- )
- def Database(self, driver, database):
- """Opening database failed"""
- GError(
- message=_(
- "Unable to open database <%(db)s> by driver <%(driver)s>. "
- "Operation canceled."
- )
- % {"db": database, "driver": driver},
- parent=self.parent,
- caption=self.caption,
- )
- def DbExecute(self, sql):
- """Sql query failed"""
- GError(
- message=_("Unable to execute SQL query '%s'. " "Operation canceled.") % sql,
- parent=self.parent,
- caption=self.caption,
- )
- def DeadLine(self, line):
- """Dead line"""
- GError(
- message=_("Feature id %d is marked as dead. " "Operation canceled.") % line,
- parent=self.parent,
- caption=self.caption,
- )
- def FeatureType(self, ftype):
- """Unknown feature type"""
- GError(
- message=_("Unsupported feature type %d. " "Operation canceled.") % ftype,
- parent=self.parent,
- caption=self.caption,
- )
- class IVDigit:
- def __init__(self, giface, mapwindow, driver=DisplayDriver):
- """Base class for vector digitizer (ctypes interface)
- :param mapwindow: reference to a map window
- """
- self.poMapInfo = None # pointer to Map_info
- self.mapWindow = mapwindow
- self._giface = giface
- # background map
- self.bgMapInfo = Map_info()
- self.poBgMapInfo = self.popoBgMapInfo = None
- try:
- progress = self._giface.GetProgress()
- except NotImplementedError:
- progress = None
- log = self._giface.GetLog(err=True)
- self.toolbar = mapwindow.parent.toolbars["vdigit"]
- self._error = VDigitError(parent=self.mapWindow)
- self._display = driver(
- device=mapwindow.pdcVector,
- deviceTmp=mapwindow.pdcTmp,
- mapObj=mapwindow.Map,
- window=mapwindow,
- glog=log,
- gprogress=progress,
- )
- # GRASS lib
- self.poPoints = Vect_new_line_struct()
- self.poCats = Vect_new_cats_struct()
- # self.SetCategory()
- # layer / max category
- self.cats = dict()
- self._settings = dict()
- self.UpdateSettings() # -> self._settings
- # undo/redo
- self.changesets = list()
- self.changesetCurrent = -1 # first changeset to apply
- if self.poMapInfo:
- self.InitCats()
- self.emit_signals = False
- # signals which describes features changes during digitization,
- # activate them using EmitSignals method
- # currently implemented for functionality used by wx.iclass (for
- # scatter plot)
- # signals parameter description:
- # old_bboxs - list of bboxes of boundary features, which covers changed areas
- # it is bbox of old state (before edit)
- # old_areas_cats - list of area categories of boundary features of old state (before edit)
- # same position in both lists corresponds to same feature
- # new_bboxs = list of bboxes of created features / after edit
- # new_areas_cats list of areas cats of created features / after edit
- # same position in both lists corresponds to same features
- # for description of items in bbox and area_cats lists see return value
- # of _getaAreaBboxCats
- # TODO currently it is not possible to identify corresponded features
- # in old and new lists (requires changed to vector updated format)
- # TODO return feature type
- # TODO handle errors?
- self.featureAdded = Signal("IVDigit.featureAdded")
- self.areasDeleted = Signal("IVDigit.areasDeleted")
- self.vertexMoved = Signal("IVDigit.vertexMoved")
- self.vertexAdded = Signal("IVDigit.vertexAdded")
- self.vertexRemoved = Signal("IVDigit.vertexRemoved")
- self.featuresDeleted = Signal("IVDigit.featuresDeleted")
- self.featuresMoved = Signal("IVDigit.featuresMoved")
- self.lineEdited = Signal("IVDigit.lineEdited")
- def __del__(self):
- Debug.msg(1, "IVDigit.__del__()")
- Vect_destroy_line_struct(self.poPoints)
- self.poPoints = None
- Vect_destroy_cats_struct(self.poCats)
- self.poCats = None
- if self.poBgMapInfo:
- Vect_close(self.poBgMapInfo)
- self.poBgMapInfo = self.popoBgMapInfo = None
- del self.bgMapInfo
- def EmitSignals(self, emit):
- """Activate/deactivate signals which describes features changes during digitization."""
- self.emit_signals = emit
- def CloseBackgroundMap(self):
- """Close background vector map"""
- if not self.poBgMapInfo:
- return
- Vect_close(self.poBgMapInfo)
- self.poBgMapInfo = self.popoBgMapInfo = None
- def OpenBackgroundMap(self, bgmap):
- """Open background vector map
- .. todo::
- support more background maps then only one
- :param bgmap: name of vector map to be opened
- :type bgmap: str
- :return: pointer to map_info
- :return: None on error
- """
- name = create_string_buffer(GNAME_MAX)
- mapset = create_string_buffer(GMAPSET_MAX)
- if not G_name_is_fully_qualified(bgmap, name, mapset):
- name = bgmap
- mapset = grass.decode(G_find_vector2(bgmap, ""))
- else:
- name = grass.decode(name.value)
- mapset = grass.decode(mapset.value)
- if name == Vect_get_name(self.poMapInfo) and mapset == Vect_get_mapset(
- self.poMapInfo
- ):
- self.poBgMapInfo = self.popoBgMapInfo = None
- self._error.NoMap(bgmap)
- return
- self.poBgMapInfo = pointer(self.bgMapInfo)
- self.popoBgMapInfo = pointer(self.poBgMapInfo)
- if Vect_open_old(self.poBgMapInfo, name, mapset) == -1:
- self.poBgMapInfo = self.popoBgMapInfo = None
- self._error.NoMap(bgmap)
- return
- def _getSnapMode(self):
- """Get snapping mode
- - snap to vertex
- - snap to nodes
- - no snapping
- :return: snap mode
- """
- threshold = self._display.GetThreshold()
- if threshold > 0.0:
- if UserSettings.Get(group="vdigit", key="snapToVertex", subkey="enabled"):
- return SNAPVERTEX
- else:
- return SNAP
- else:
- return NO_SNAP
- def _getNewFeaturesLayer(self):
- """Returns layer of new feature (from settings)"""
- if (
- UserSettings.Get(group="vdigit", key="categoryMode", subkey="selection")
- == 2
- ):
- layer = -1 # -> no category
- else:
- layer = UserSettings.Get(group="vdigit", key="layer", subkey="value")
- return layer
- def _getNewFeaturesCat(self):
- """Returns category of new feature (from settings)"""
- if (
- UserSettings.Get(group="vdigit", key="categoryMode", subkey="selection")
- == 2
- ):
- cat = -1
- else:
- cat = self.SetCategory()
- return cat
- def _breakLineAtIntersection(self, line, pointsLine):
- """Break given line at intersection
- :param line: line id
- :param pointsLine: line geometry
- :return: number of modified lines
- """
- if not self._checkMap():
- return -1
- if not Vect_line_alive(self.poMapInfo, line):
- return 0
- if not pointsLine:
- if Vect_read_line(self.poMapInfo, self.poPoints, None, line) < 0:
- self._error.ReadLine(line)
- return -1
- points = self.poPoints
- else:
- points = pointsLine
- listLine = Vect_new_boxlist(0)
- listRef = Vect_new_list()
- listBreak = Vect_new_list()
- pointsCheck = Vect_new_line_struct()
- lineBox = bound_box()
- # find all relevant lines
- Vect_get_line_box(self.poMapInfo, line, byref(lineBox))
- Vect_select_lines_by_box(self.poMapInfo, byref(lineBox), GV_LINES, listLine)
- # check for intersection
- Vect_list_append(listBreak, line)
- Vect_list_append(listRef, line)
- for i in range(listLine.contents.n_values):
- lineBreak = listLine.contents.id[i]
- if lineBreak == line:
- continue
- ltype = Vect_read_line(self.poMapInfo, pointsCheck, None, lineBreak)
- if not (ltype & GV_LINES):
- continue
- if Vect_line_check_intersection(self.poPoints, pointsCheck, WITHOUT_Z):
- Vect_list_append(listBreak, lineBreak)
- ret = Vect_break_lines_list(self.poMapInfo, listBreak, listRef, GV_LINES, None)
- Vect_destroy_line_struct(pointsCheck)
- Vect_destroy_boxlist(listLine)
- Vect_destroy_list(listBreak)
- Vect_destroy_list(listRef)
- return ret
- def _addChangeset(self):
- # disable redo
- changesetLast = len(self.changesets) - 1
- if self.changesetCurrent < changesetLast and len(self.changesets) > 0:
- del self.changesets[self.changesetCurrent + 1 : changesetLast + 1]
- self.toolbar.EnableRedo(False)
- data = list()
- for i in range(Vect_get_num_updated_lines(self.poMapInfo) - 1, -1, -1):
- line = Vect_get_updated_line(self.poMapInfo, i)
- offset = Vect_get_updated_line_offset(self.poMapInfo, i)
- data.append({"line": line, "offset": offset})
- self.changesetCurrent += 1
- self.changesets.insert(self.changesetCurrent, data)
- Vect_reset_updated(self.poMapInfo)
- def _applyChangeset(self, changeset, undo):
- """Apply changeset (undo/redo changeset)
- :param changeset: changeset id
- :param undo: True for undo otherwise redo
- :type undo: bool
- :return: 1 changeset applied
- :return: 0 changeset not applied
- :return: -1 on error
- """
- if changeset < 0 or changeset >= len(self.changesets):
- return -1
- ret = 0
- actions = self.changesets[changeset]
- if undo:
- firstaction = 0
- lastaction = len(actions)
- step = 1
- else:
- firstaction = len(actions) - 1
- lastaction = -1
- step = -1
- for i in range(firstaction, lastaction, step):
- action = actions[i]
- line = action["line"]
- if action["offset"] > 0:
- # feature previously added -> to be deleted
- if Vect_line_alive(self.poMapInfo, line):
- Debug.msg(
- 3,
- "IVDigit._applyChangeset(): changeset=%d, action=add, line=%d -> deleted",
- changeset,
- line,
- )
- Vect_delete_line(self.poMapInfo, line)
- ret = 1
- else:
- Debug.msg(
- 3,
- "Digit.ApplyChangeset(): changeset=%d, action=add, line=%d dead",
- changeset,
- line,
- )
- else:
- # feature previously deleted -> to be added
- offset = abs(action["offset"])
- if not Vect_line_alive(self.poMapInfo, line):
- Debug.msg(
- 3,
- "Digit.ApplyChangeset(): changeset=%d, action=delete, line=%d -> added",
- changeset,
- line,
- )
- if Vect_restore_line(self.poMapInfo, offset, line) < 0:
- return -1
- ret = 1
- else:
- Debug.msg(
- 3,
- "Digit.ApplyChangeset(): changeset=%d, action=delete, line=%d alive",
- changeset,
- line,
- )
- action["offset"] *= -1
- Vect_reset_updated(self.poMapInfo)
- return ret
- def AddFeature(self, ftype, points):
- """Add new feature
- :param ftype: feature type (point, line, centroid, boundary)
- :param points: tuple of points ((x, y), (x, y), ...)
- :return: tuple (number of added features, feature ids)
- """
- layer = self._getNewFeaturesLayer()
- cat = self._getNewFeaturesCat()
- if ftype == "point":
- vtype = GV_POINT
- elif ftype == "line":
- vtype = GV_LINE
- elif ftype == "centroid":
- vtype = GV_CENTROID
- elif ftype == "boundary":
- vtype = GV_BOUNDARY
- elif ftype == "area":
- vtype = GV_AREA
- else:
- GError(
- parent=self.mapWindow, message=_("Unknown feature type '%s'") % ftype
- )
- return (-1, None)
- if vtype & GV_LINES and len(points) < 2:
- GError(parent=self.mapWindow, message=_("Not enough points for line"))
- return (-1, None)
- self.toolbar.EnableUndo()
- ret = self._addFeature(
- vtype, points, layer, cat, self._getSnapMode(), self._display.GetThreshold()
- )
- if ret[0] > -1 and self.emit_signals:
- self.featureAdded.emit(
- new_bboxs=[self._createBbox(points)],
- new_areas_cats=[[{layer: [cat]}, None]],
- )
- return ret
- def DeleteSelectedLines(self):
- """Delete selected features
- :return: number of deleted features
- """
- if not self._checkMap():
- return -1
- # collect categories for deleting if requested
- deleteRec = UserSettings.Get(group="vdigit", key="delRecord", subkey="enabled")
- catDict = dict()
- old_bboxs = []
- old_areas_cats = []
- if deleteRec:
- for i in self._display.selected["ids"]:
- if Vect_read_line(self.poMapInfo, None, self.poCats, i) < 0:
- self._error.ReadLine(i)
- if self.emit_signals:
- ret = self._getLineAreaBboxCats(i)
- if ret:
- old_bboxs += ret[0]
- old_areas_cats += ret[1]
- # catDict was not used -> put into comment
- # cats = self.poCats.contents
- # for j in range(cats.n_cats):
- # if cats.field[j] not in catDict.keys():
- # catDict[cats.field[j]] = list()
- # catDict[cats.field[j]].append(cats.cat[j])
- poList = self._display.GetSelectedIList()
- nlines = Vedit_delete_lines(self.poMapInfo, poList)
- Vect_destroy_list(poList)
- self._display.selected["ids"] = list()
- if nlines > 0:
- if deleteRec:
- self._deleteRecords(catDict)
- self._addChangeset()
- self.toolbar.EnableUndo()
- if self.emit_signals:
- self.featuresDeleted.emit(
- old_bboxs=old_bboxs, old_areas_cats=old_areas_cats
- )
- return nlines
- def _deleteRecords(self, cats):
- """Delete records from attribute table
- :param cats: directory field/list of cats
- """
- handle = dbHandle()
- poHandle = pointer(handle)
- stmt = dbString()
- poStmt = pointer(stmt)
- for dblink in range(Vect_get_num_dblinks(self.poMapInfo)):
- poFi = Vect_get_dblink(self.poMapInfo, dblink)
- if poFi is None:
- self._error.DbLink(dblink)
- return -1
- Fi = poFi.contents
- if Fi.number not in cats.keys():
- continue
- poDriver = db_start_driver(Fi.driver)
- if poDriver is None:
- self._error.Driver(Fi.driver)
- return -1
- db_init_handle(poHandle)
- db_set_handle(poHandle, Fi.database, None)
- if db_open_database(poDriver, poHandle) != DB_OK:
- self._error.Database(Fi.driver, Fi.database)
- return -1
- db_init_string(poStmt)
- db_set_string(poStmt, "DELETE FROM %s WHERE" % Fi.table)
- n_cats = 0
- for cat in cats[Fi.number]:
- if n_cats > 0:
- db_append_string(poStmt, " or")
- db_append_string(poStmt, " %s = %d" % (Fi.key, cat))
- n_cats += 1
- if n_cats > 0 and db_execute_immediate(poDriver, poStmt) != DB_OK:
- self._error.DbExecute(db_get_string(poStmt))
- return -1
- db_close_database_shutdown_driver(poDriver)
- def DeleteSelectedAreas(self):
- """Delete selected areas (centroid+boundaries)
- :return: number of deleted
- """
- if len(self._display.selected["ids"]) < 1:
- return 0
- poList = self._display.GetSelectedIList()
- cList = poList.contents
- nareas = 0
- old_bboxs = []
- old_areas_cats = []
- for i in range(cList.n_values):
- if Vect_get_line_type(self.poMapInfo, cList.value[i]) != GV_CENTROID:
- continue
- if self.emit_signals:
- area = Vect_get_centroid_area(self.poMapInfo, cList.value[i])
- if area > 0:
- bbox, cats = self._getaAreaBboxCats(area)
- old_bboxs += bbox
- old_areas_cats += cats
- nareas += Vedit_delete_area_centroid(self.poMapInfo, cList.value[i])
- if nareas > 0:
- self._addChangeset()
- self.toolbar.EnableUndo()
- if self.emit_signals:
- self.areasDeleted.emit(
- old_bboxs=old_bboxs, old_areas_cats=old_areas_cats
- )
- return nareas
- def _getLineAreaBboxCats(self, ln_id):
- """Helper function
- :param ln_id: id of feature
- :return: None if the feature does not exists
- :return: list of :func:`_getaAreaBboxCats`
- """
- ltype = Vect_read_line(self.poMapInfo, None, None, ln_id)
- if ltype == GV_CENTROID:
- # TODO centroid opttimization, can be edited also its area -> it
- # will appear two times in new_ lists
- return self._getCentroidAreaBboxCats(ln_id)
- else:
- return [self._getBbox(ln_id)], [self._getLineAreasCategories(ln_id)]
- def _getCentroidAreaBboxCats(self, centroid):
- """Helper function
- :param centroid: id of an centroid
- :return: None if area does not exists
- :return: see return of :func:`_getaAreaBboxCats`
- """
- if not Vect_line_alive(self.poMapInfo, centroid):
- return None
- area = Vect_get_centroid_area(self.poMapInfo, centroid)
- if area > 0:
- return self._getaAreaBboxCats(area)
- else:
- return None
- def _getaAreaBboxCats(self, area):
- """Helper function
- :param area: area id
- :return: list of categories :func:`_getLineAreasCategories` and
- list of bboxes :func:`_getBbox` of area boundary
- features
- """
- po_b_list = Vect_new_list()
- Vect_get_area_boundaries(self.poMapInfo, area, po_b_list)
- b_list = po_b_list.contents
- geoms = []
- areas_cats = []
- if b_list.n_values > 0:
- for i_line in range(b_list.n_values):
- line = b_list.value[i_line]
- geoms.append(self._getBbox(abs(line)))
- areas_cats.append(self._getLineAreasCategories(abs(line)))
- Vect_destroy_list(po_b_list)
- return geoms, areas_cats
- def _getLineAreasCategories(self, ln_id):
- """Helper function
- :param line_id: id of boundary feature
- :return: categories of areas on the left, right side of the feature
- :return: format: [[{layer : [cat]}, None]] means:
- area to the left (list of layers which has cats list as values),
- area to the right (no area there in this case (None))
- :return: [] the feature is not boundary or does not exists
- """
- if not Vect_line_alive(self.poMapInfo, ln_id):
- return []
- ltype = Vect_read_line(self.poMapInfo, None, None, ln_id)
- if ltype != GV_BOUNDARY:
- return []
- cats = [None, None]
- left = c_int()
- right = c_int()
- if (
- Vect_get_line_areas(self.poMapInfo, ln_id, pointer(left), pointer(right))
- == 1
- ):
- areas = [left.value, right.value]
- for i, a in enumerate(areas):
- if a > 0:
- centroid = Vect_get_area_centroid(self.poMapInfo, a)
- if centroid <= 0:
- continue
- c = self._getCategories(centroid)
- if c:
- cats[i] = c
- return cats
- def _getCategories(self, ln_id):
- """Helper function
- :param line_id: id of feature
- :return: list of the feature categories [{layer : cats}, next layer...]
- :return: None feature does not exist
- """
- if not Vect_line_alive(self.poMapInfo, ln_id):
- return none
- poCats = Vect_new_cats_struct()
- if Vect_read_line(self.poMapInfo, None, poCats, ln_id) < 0:
- Vect_destroy_cats_struct(poCats)
- return None
- cCats = poCats.contents
- cats = {}
- for j in range(cCats.n_cats):
- if cCats.field[j] in cats:
- cats[cCats.field[j]].append(cCats.cat[j])
- else:
- cats[cCats.field[j]] = [cCats.cat[j]]
- Vect_destroy_cats_struct(poCats)
- return cats
- def _getBbox(self, ln_id):
- """Helper function
- :param line_id: id of line feature
- :return: bbox bounding box of the feature
- :return: None feature does not exist
- """
- if not Vect_line_alive(self.poMapInfo, ln_id):
- return None
- poPoints = Vect_new_line_struct()
- if Vect_read_line(self.poMapInfo, poPoints, None, ln_id) < 0:
- Vect_destroy_line_struct(poPoints)
- return []
- geom = self._convertGeom(poPoints)
- bbox = self._createBbox(geom)
- Vect_destroy_line_struct(poPoints)
- return bbox
- def _createBbox(self, points):
- """Helper function
- :param points: list of points [(x, y), ...] to be bbox created for
- :return: bbox bounding box of points {'maxx':, 'maxy':, 'minx':, 'miny'}
- """
- bbox = {}
- for pt in points:
- if "maxy" not in bbox:
- bbox["maxy"] = pt[1]
- bbox["miny"] = pt[1]
- bbox["maxx"] = pt[0]
- bbox["minx"] = pt[0]
- continue
- if bbox["maxy"] < pt[1]:
- bbox["maxy"] = pt[1]
- elif bbox["miny"] > pt[1]:
- bbox["miny"] = pt[1]
- if bbox["maxx"] < pt[0]:
- bbox["maxx"] = pt[0]
- elif bbox["minx"] > pt[0]:
- bbox["minx"] = pt[0]
- return bbox
- def _convertGeom(self, poPoints):
- """Helper function convert geom from ctypes line_pts to python
- list
- :return: coords in python list [(x, y),...]
- """
- Points = poPoints.contents
- pts_geom = []
- for j in range(Points.n_points):
- pts_geom.append((Points.x[j], Points.y[j]))
- return pts_geom
- def MoveSelectedLines(self, move):
- """Move selected features
- :param move: direction (x, y)
- """
- if not self._checkMap():
- return -1
- nsel = len(self._display.selected["ids"])
- if nsel < 1:
- return -1
- thresh = self._display.GetThreshold()
- snap = self._getSnapMode()
- poList = self._display.GetSelectedIList()
- if self.emit_signals:
- old_bboxs = []
- old_areas_cats = []
- for sel_id in self._display.selected["ids"]:
- ret = self._getLineAreaBboxCats(sel_id)
- if ret:
- old_bboxs += ret[0]
- old_areas_cats += ret[1]
- Vect_set_updated(self.poMapInfo, 1)
- n_up_lines_old = Vect_get_num_updated_lines(self.poMapInfo)
- nlines = Vedit_move_lines(
- self.poMapInfo,
- self.popoBgMapInfo,
- int(self.poBgMapInfo is not None),
- poList,
- move[0],
- move[1],
- 0,
- snap,
- thresh,
- )
- Vect_destroy_list(poList)
- if nlines > 0 and self.emit_signals:
- new_bboxs = []
- new_areas_cats = []
- n_up_lines = Vect_get_num_updated_lines(self.poMapInfo)
- for i in range(n_up_lines_old, n_up_lines):
- new_id = Vect_get_updated_line(self.poMapInfo, i)
- ret = self._getLineAreaBboxCats(new_id)
- if ret:
- new_bboxs += ret[0]
- new_areas_cats += ret[1]
- if nlines > 0 and self._settings["breakLines"]:
- for i in range(1, nlines):
- self._breakLineAtIntersection(nlines + i, None, changeset)
- if nlines > 0:
- self._addChangeset()
- self.toolbar.EnableUndo()
- if self.emit_signals:
- self.featuresMoved.emit(
- new_bboxs=new_bboxs,
- old_bboxs=old_bboxs,
- old_areas_cats=old_areas_cats,
- new_areas_cats=new_areas_cats,
- )
- return nlines
- def MoveSelectedVertex(self, point, move):
- """Move selected vertex of the line
- :param point: location point
- :param move: x,y direction
- :return: id of new feature
- :return: 0 vertex not moved (not found, line is not selected)
- :return: -1 on error
- """
- if not self._checkMap():
- return -1
- if len(self._display.selected["ids"]) != 1:
- return -1
- # move only first found vertex in bbox
- poList = self._display.GetSelectedIList()
- if self.emit_signals:
- cList = poList.contents
- old_bboxs = [self._getBbox(cList.value[0])]
- old_areas_cats = [self._getLineAreasCategories(cList.value[0])]
- Vect_set_updated(self.poMapInfo, 1)
- n_up_lines_old = Vect_get_num_updated_lines(self.poMapInfo)
- Vect_reset_line(self.poPoints)
- Vect_append_point(self.poPoints, point[0], point[1], 0.0)
- moved = Vedit_move_vertex(
- self.poMapInfo,
- self.popoBgMapInfo,
- int(self.poBgMapInfo is not None),
- poList,
- self.poPoints,
- self._display.GetThreshold(type="selectThresh"),
- self._display.GetThreshold(),
- move[0],
- move[1],
- 0.0,
- 1,
- self._getSnapMode(),
- )
- Vect_destroy_list(poList)
- if moved > 0 and self.emit_signals:
- n_up_lines = Vect_get_num_updated_lines(self.poMapInfo)
- new_bboxs = []
- new_areas_cats = []
- for i in range(n_up_lines_old, n_up_lines):
- new_id = Vect_get_updated_line(self.poMapInfo, i)
- new_bboxs.append(self._getBbox(new_id))
- new_areas_cats.append(self._getLineAreasCategories(new_id))
- if moved > 0 and self._settings["breakLines"]:
- self._breakLineAtIntersection(Vect_get_num_lines(self.poMapInfo), None)
- if moved > 0:
- self._addChangeset()
- self.toolbar.EnableUndo()
- if self.emit_signals:
- self.vertexMoved.emit(
- new_bboxs=new_bboxs,
- new_areas_cats=new_areas_cats,
- old_areas_cats=old_areas_cats,
- old_bboxs=old_bboxs,
- )
- return moved
- def AddVertex(self, coords):
- """Add new vertex to the selected line/boundary on position 'coords'
- :param coords: coordinates to add vertex
- :return: id of new feature
- :return: 0 nothing changed
- :return: -1 on failure
- """
- added = self._ModifyLineVertex(coords, add=True)
- if added > 0:
- self.toolbar.EnableUndo()
- return added
- def RemoveVertex(self, coords):
- """Remove vertex from the selected line/boundary on position 'coords'
- :param coords: coordinates to remove vertex
- :return: id of new feature
- :return: 0 nothing changed
- :return: -1 on failure
- """
- deleted = self._ModifyLineVertex(coords, add=False)
- if deleted > 0:
- self.toolbar.EnableUndo()
- return deleted
- def SplitLine(self, point):
- """Split/break selected line/boundary on given position
- :param point: point where to split line
- :return: 1 line modified
- :return: 0 nothing changed
- :return: -1 error
- """
- thresh = self._display.GetThreshold("selectThresh")
- if not self._checkMap():
- return -1
- poList = self._display.GetSelectedIList()
- Vect_reset_line(self.poPoints)
- Vect_append_point(self.poPoints, point[0], point[1], 0.0)
- ret = Vedit_split_lines(self.poMapInfo, poList, self.poPoints, thresh, None)
- Vect_destroy_list(poList)
- if ret > 0:
- self._addChangeset()
- self.toolbar.EnableUndo()
- return ret
- def EditLine(self, line, coords):
- """Edit existing line/boundary
- :param line: feature id to be modified
- :param coords: list of coordinates of modified line
- :return: feature id of new line
- :return: -1 on error
- """
- if not self._checkMap():
- return -1
- if len(coords) < 2:
- self.DeleteSelectedLines()
- return 0
- if not Vect_line_alive(self.poMapInfo, line):
- self._error.DeadLine(line)
- return -1
- # read original feature
- ltype = Vect_read_line(self.poMapInfo, None, self.poCats, line)
- if ltype < 0:
- self._error.ReadLine(line)
- return -1
- if self.emit_signals:
- old_bboxs = [self._getBbox(line)]
- old_areas_cats = [self._getLineAreasCategories(line)]
- # build feature geometry
- Vect_reset_line(self.poPoints)
- for p in coords:
- Vect_append_point(self.poPoints, p[0], p[1], 0.0)
- # apply snapping (node or vertex)
- snap = self._getSnapMode()
- if snap != NO_SNAP:
- modeSnap = not (snap == SNAP)
- Vedit_snap_line(
- self.poMapInfo,
- self.popoBgMapInfo,
- int(self.poBgMapInfo is not None),
- -1,
- self.poPoints,
- self._display.GetThreshold(),
- modeSnap,
- )
- newline = Vect_rewrite_line(
- self.poMapInfo, line, ltype, self.poPoints, self.poCats
- )
- if newline > 0 and self.emit_signals:
- new_geom = [self._getBbox(newline)]
- new_areas_cats = [self._getLineAreasCategories(newline)]
- if newline > 0 and self._settings["breakLines"]:
- self._breakLineAtIntersection(newline, None)
- if newline > 0:
- self._addChangeset()
- self.toolbar.EnableUndo()
- if self.emit_signals:
- self.lineEdited.emit(
- old_bboxs=old_bboxs,
- old_areas_cats=old_areas_cats,
- new_bboxs=new_bboxs,
- new_areas_cats=new_areas_cats,
- )
- return newline
- def FlipLine(self):
- """Flip selected lines/boundaries
- :return: number of modified lines
- :return: -1 on error
- """
- if not self._checkMap():
- return -1
- nlines = Vect_get_num_lines(self.poMapInfo)
- poList = self._display.GetSelectedIList()
- ret = Vedit_flip_lines(self.poMapInfo, poList)
- Vect_destroy_list(poList)
- if ret > 0:
- self._addChangeset()
- self.toolbar.EnableUndo()
- return ret
- def MergeLine(self):
- """Merge selected lines/boundaries
- :return: number of modified lines
- :return: -1 on error
- """
- if not self._checkMap():
- return -1
- poList = self._display.GetSelectedIList()
- ret = Vedit_merge_lines(self.poMapInfo, poList)
- Vect_destroy_list(poList)
- if ret > 0:
- self._addChangeset()
- self.toolbar.EnableUndo()
- return ret
- def BreakLine(self):
- """Break selected lines/boundaries
- :return: number of modified lines
- :return: -1 on error
- """
- if not self._checkMap():
- return -1
- poList = self._display.GetSelectedIList()
- ret = Vect_break_lines_list(self.poMapInfo, poList, None, GV_LINES, None)
- Vect_destroy_list(poList)
- if ret > 0:
- self._addChangeset()
- self.toolbar.EnableUndo()
- return ret
- def SnapLine(self):
- """Snap selected lines/boundaries
- :return: 0 on success
- :return: -1 on error
- """
- if not self._checkMap():
- return -1
- nlines = Vect_get_num_lines(self.poMapInfo)
- poList = self._display.GetSelectedIList()
- Vect_snap_lines_list(self.poMapInfo, poList, self._display.GetThreshold(), None)
- Vect_destroy_list(poList)
- if nlines < Vect_get_num_lines(self.poMapInfo):
- self._addChangeset()
- self.toolbar.EnableUndo()
- return 0
- def ConnectLine(self):
- """Connect selected lines/boundaries
- :return: 1 lines connected
- :return: 0 lines not connected
- :return: -1 on error
- """
- if not self._checkMap():
- return -1
- poList = self._display.GetSelectedIList()
- ret = Vedit_connect_lines(self.poMapInfo, poList, self._display.GetThreshold())
- Vect_destroy_list(poList)
- if ret > 0:
- self._addChangeset()
- self.toolbar.EnableUndo()
- return ret
- def CopyLine(self, ids=[]):
- """Copy features from (background) vector map
- :param ids: list of line ids to be copied
- :return: number of copied features
- :return: -1 on error
- """
- if not self._checkMap():
- return -1
- nlines = Vect_get_num_lines(self.poMapInfo)
- poList = self._display.GetSelectedIList(ids)
- ret = Vedit_copy_lines(self.poMapInfo, self.poBgMapInfo, poList)
- Vect_destroy_list(poList)
- if ret > 0 and self.poBgMapInfo and self._settings["breakLines"]:
- for i in range(1, ret):
- self._breakLineAtIntersection(nlines + i, None)
- if ret > 0:
- self._addChangeset()
- self.toolbar.EnableUndo()
- return ret
- def CopyCats(self, fromId, toId, copyAttrb=False):
- """Copy given categories to objects with id listed in ids
- :param cats: ids of 'from' feature
- :param ids: ids of 'to' feature(s)
- :return: number of modified features
- :return: -1 on error
- """
- if len(fromId) < 1 or len(toId) < 1:
- return 0
- poCatsFrom = self.poCats
- poCatsTo = Vect_new_cats_struct()
- nlines = 0
- for fline in fromId:
- if not Vect_line_alive(self.poMapInfo, fline):
- continue
- if Vect_read_line(self.poMapInfo, None, poCatsFrom, fline) < 0:
- self._error.ReadLine(fline)
- return -1
- for tline in toId:
- if not Vect_line_alive(self.poMapInfo, tline):
- continue
- ltype = Vect_read_line(self.poMapInfo, self.poPoints, poCatsTo, tline)
- if ltype < 0:
- self._error.ReadLine(fline)
- return -1
- catsFrom = poCatsFrom.contents
- for i in range(catsFrom.n_cats):
- if not copyAttrb:
- # duplicate category
- cat = catsFrom.cat[i]
- else:
- # duplicate attributes
- cat = self.cats[catsFrom.field[i]] + 1
- self.cats[catsFrom.field[i]] = cat
- poFi = Vect_get_field(self.poMapInfo, catsFrom.field[i])
- if not poFi:
- self._error.DbLink(i)
- return -1
- fi = poFi.contents
- driver = db_start_driver(fi.driver)
- if not driver:
- self._error.Driver(fi.driver)
- return -1
- handle = dbHandle()
- db_init_handle(byref(handle))
- db_set_handle(byref(handle), fi.database, None)
- if db_open_database(driver, byref(handle)) != DB_OK:
- db_shutdown_driver(driver)
- self._error.Database(fi.driver, fi.database)
- return -1
- stmt = dbString()
- db_init_string(byref(stmt))
- db_set_string(
- byref(stmt),
- "SELECT * FROM %s WHERE %s=%d"
- % (fi.table, fi.key, catsFrom.cat[i]),
- )
- cursor = dbCursor()
- if (
- db_open_select_cursor(
- driver, byref(stmt), byref(cursor), DB_SEQUENTIAL
- )
- != DB_OK
- ):
- db_close_database_shutdown_driver(driver)
- return -1
- table = db_get_cursor_table(byref(cursor))
- ncols = db_get_table_number_of_columns(table)
- sql = "INSERT INTO %s VALUES (" % fi.table
- # fetch the data
- more = c_int()
- while True:
- if db_fetch(byref(cursor), DB_NEXT, byref(more)) != DB_OK:
- db_close_database_shutdown_driver(driver)
- return -1
- if not more.value:
- break
- value_string = dbString()
- for col in range(ncols):
- if col > 0:
- sql += ","
- column = db_get_table_column(table, col)
- if db_get_column_name(column) == fi.key:
- sql += "%d" % cat
- continue
- value = db_get_column_value(column)
- db_convert_column_value_to_string(
- column, byref(value_string)
- )
- if db_test_value_isnull(value):
- sql += "NULL"
- else:
- ctype = db_sqltype_to_Ctype(
- db_get_column_sqltype(column)
- )
- if ctype != DB_C_TYPE_STRING:
- sql += db_get_string(byref(value_string))
- else:
- sql += "'%s'" % db_get_string(
- byref(value_string)
- )
- sql += ")"
- db_set_string(byref(stmt), sql)
- if db_execute_immediate(driver, byref(stmt)) != DB_OK:
- db_close_database_shutdown_driver(driver)
- return -1
- db_close_database_shutdown_driver(driver)
- G_free(poFi)
- if Vect_cat_set(poCatsTo, catsFrom.field[i], cat) < 1:
- continue
- if (
- Vect_rewrite_line(
- self.poMapInfo, tline, ltype, self.poPoints, poCatsTo
- )
- < 0
- ):
- self._error.WriteLine()
- return -1
- nlines += 1
- Vect_destroy_cats_struct(poCatsTo)
- if nlines > 0:
- self.toolbar.EnableUndo()
- return nlines
- def _selectLinesByQueryThresh(self):
- """Generic method used for SelectLinesByQuery() -- to get
- threshold value"""
- thresh = 0.0
- if UserSettings.Get(group="vdigit", key="query", subkey="selection") == 0:
- thresh = UserSettings.Get(
- group="vdigit", key="queryLength", subkey="thresh"
- )
- if (
- UserSettings.Get(
- group="vdigit", key="queryLength", subkey="than-selection"
- )
- == 0
- ):
- thresh = -1 * thresh
- else:
- thresh = UserSettings.Get(
- group="vdigit", key="queryDangle", subkey="thresh"
- )
- if (
- UserSettings.Get(
- group="vdigit", key="queryDangle", subkey="than-selection"
- )
- == 0
- ):
- thresh = -1 * thresh
- return thresh
- def SelectLinesByQuery(self, bbox):
- """Select features by query
- .. todo::
- layer / 3D
- :param bbox: bounding box definition
- """
- if not self._checkMap():
- return -1
- thresh = self._selectLinesByQueryThresh()
- query = QUERY_UNKNOWN
- if UserSettings.Get(group="vdigit", key="query", subkey="selection") == 0:
- query = QUERY_LENGTH
- else:
- query = QUERY_DANGLE
- ftype = GV_POINTS | GV_LINES # TODO: 3D
- layer = 1 # TODO
- ids = list()
- poList = Vect_new_list()
- coList = poList.contents
- if UserSettings.Get(group="vdigit", key="query", subkey="box"):
- Vect_reset_line(self.poPoints)
- x1, y1 = bbox[0]
- x2, y2 = bbox[1]
- z1 = z2 = 0.0
- Vect_append_point(self.poPoints, x1, y1, z1)
- Vect_append_point(self.poPoints, x2, y1, z2)
- Vect_append_point(self.poPoints, x2, y2, z1)
- Vect_append_point(self.poPoints, x1, y2, z2)
- Vect_append_point(self.poPoints, x1, y1, z1)
- Vect_select_lines_by_polygon(
- self.poMapInfo, self.poPoints, 0, None, ftype, poList
- )
- if coList.n_values == 0:
- return ids
- Vedit_select_by_query(self.poMapInfo, ftype, layer, thresh, query, poList)
- for i in range(coList.n_values):
- ids.append(int(coList.value[i]))
- Debug.msg(3, "IVDigit.SelectLinesByQuery(): lines=%d", coList.n_values)
- Vect_destroy_list(poList)
- return ids
- def IsVector3D(self):
- """Check if open vector map is 3D"""
- if not self._checkMap():
- return False
- return Vect_is_3d(self.poMapInfo)
- def GetLineLength(self, line):
- """Get line length
- :param line: feature id
- :return: line length
- :return: -1 on error
- """
- if not self._checkMap():
- return -1
- if not Vect_line_alive(self.poMapInfo, line):
- return -1
- ltype = Vect_read_line(self.poMapInfo, self.poPoints, None, line)
- if ltype < 0:
- self._error.ReadLine(line)
- return ret
- length = -1
- if ltype & GV_LINES: # lines & boundaries
- length = Vect_line_length(self.poPoints)
- return length
- def GetAreaSize(self, centroid):
- """Get area size
- :param centroid: centroid id
- :return: area size
- :return: -1 on error
- """
- if not self._checkMap():
- return -1
- ltype = Vect_read_line(self.poMapInfo, None, None, centroid)
- if ltype < 0:
- self._error.ReadLine(line)
- return ret
- if ltype != GV_CENTROID:
- return -1
- area = Vect_get_centroid_area(self.poMapInfo, centroid)
- size = -1
- if area > 0:
- if not Vect_area_alive(self.poMapInfo, area):
- return size
- size = Vect_get_area_area(self.poMapInfo, area)
- return size
- def GetAreaPerimeter(self, centroid):
- """Get area perimeter
- :param centroid: centroid id
- :return: area size
- :return: -1 on error
- """
- if not self._checkMap():
- return -1
- ltype = Vect_read_line(self.poMapInfo, None, None, centroid)
- if ltype < 0:
- self._error.ReadLine(line)
- return ret
- if ltype != GV_CENTROID:
- return -1
- area = Vect_get_centroid_area(self.poMapInfo, centroid)
- perimeter = -1
- if area > 0:
- if not Vect_area_alive(self.poMapInfo, area):
- return -1
- Vect_get_area_points(self.poMapInfo, area, self.poPoints)
- perimeter = Vect_area_perimeter(self.poPoints)
- return perimeter
- def SetLineCats(self, line, layer, cats, add=True):
- """Set categories for given line and layer
- :param line: feature id
- :param layer: layer number (-1 for first selected line)
- :param cats: list of categories
- :param add: if True to add, otherwise do delete categories
- :return: new feature id (feature need to be rewritten)
- :return: -1 on error
- """
- if not self._checkMap():
- return -1
- if line < 1 and len(self._display.selected["ids"]) < 1:
- return -1
- update = False
- if line == -1:
- update = True
- line = self._display.selected["ids"][0]
- if not Vect_line_alive(self.poMapInfo, line):
- return -1
- ltype = Vect_read_line(self.poMapInfo, self.poPoints, self.poCats, line)
- if ltype < 0:
- self._error.ReadLine(line)
- return -1
- for c in cats:
- if add:
- Vect_cat_set(self.poCats, layer, c)
- else:
- Vect_field_cat_del(self.poCats, layer, c)
- newline = Vect_rewrite_line(
- self.poMapInfo, line, ltype, self.poPoints, self.poCats
- )
- if newline > 0:
- self._addChangeset()
- self.toolbar.EnableUndo()
- if update:
- # update line id since the line was rewritten
- self._display.selected["ids"][0] = newline
- return newline
- def TypeConvForSelectedLines(self):
- """Feature type conversion for selected objects.
- Supported conversions:
- - point <-> centroid
- - line <-> boundary
- :return: number of modified features
- :return: -1 on error
- """
- if not self._checkMap():
- return -1
- poList = self._display.GetSelectedIList()
- ret = Vedit_chtype_lines(self.poMapInfo, poList)
- Vect_destroy_list(poList)
- if ret > 0:
- self._addChangeset()
- self.toolbar.EnableUndo()
- return ret
- def Undo(self, level=-1):
- """Undo action
- :param level: levels to undo (0 to revert all)
- :return: id of current changeset
- """
- changesetLast = len(self.changesets) - 1
- if changesetLast < 0:
- return changesetLast
- if level < 0 and self.changesetCurrent > changesetLast:
- self.changesetCurrent = changesetLast
- elif level == 0:
- # 0 -> undo all
- level = -1 * self.changesetCurrent - 1
- Debug.msg(
- 2,
- "Digit.Undo(): changeset_last=%d, changeset_current=%d, level=%d",
- changesetLast,
- self.changesetCurrent,
- level,
- )
- if level < 0: # undo
- if self.changesetCurrent + level < -1:
- return self.changesetCurrent
- for changeset in range(
- self.changesetCurrent, self.changesetCurrent + level, -1
- ):
- self._applyChangeset(changeset, undo=True)
- elif level > 0: # redo
- if self.changesetCurrent + 1 > changesetLast:
- return self.changesetCurrent
- for changeset in range(
- self.changesetCurrent + 1, self.changesetCurrent + 1 + level
- ):
- self._applyChangeset(changeset, undo=False)
- self.changesetCurrent += level
- Debug.msg(
- 2,
- "Digit.Undo(): changeset_current=%d, changeset_last=%d",
- self.changesetCurrent,
- changesetLast,
- )
- self.mapWindow.UpdateMap(render=False)
- if self.changesetCurrent < 0: # disable undo tool
- self.toolbar.EnableUndo(False)
- else:
- self.toolbar.EnableUndo(True)
- if self.changesetCurrent < changesetLast:
- self.toolbar.EnableRedo(True)
- else:
- self.toolbar.EnableRedo(False)
- def ZBulkLines(self, pos1, pos2, start, step):
- """Z-bulk labeling
- :param pos1: reference line (start point)
- :param pos1: reference line (end point)
- :param start: starting value
- :param step: step value
- :return: number of modified lines
- :return: -1 on error
- """
- if not self._checkMap():
- return -1
- poList = self._display.GetSelectedIList()
- ret = Vedit_bulk_labeling(
- self.poMapInfo, poList, pos1[0], pos1[1], pos2[0], pos2[1], start, step
- )
- Vect_destroy_list(poList)
- if ret > 0:
- self._addChangeset()
- self.toolbar.EnableUndo()
- return ret
- def GetDisplay(self):
- """Get display driver instance"""
- return self._display
- def OpenMap(self, name, update=True, tmp=False):
- """Open vector map for editing
- :param map: name of vector map to be set up
- :type map: str
- :param tmp: True to open temporary vector map
- """
- Debug.msg(3, "AbstractDigit.SetMapName map=%s" % name)
- if "@" in name:
- name, mapset = name.split("@")
- else:
- mapset = grass.gisenv()["MAPSET"]
- self.poMapInfo = self._display.OpenMap(str(name), str(mapset), update, tmp)
- if self.poMapInfo:
- self.InitCats()
- return self.poMapInfo
- def CloseMap(self):
- """Close currently open vector map"""
- if not self._checkMap():
- return
- # print extra line before building message
- sys.stdout.write(os.linesep)
- # build topology, close map
- self._display.CloseMap()
- def InitCats(self):
- """Initialize categories information
- :return: 0 on success
- :return: -1 on error
- """
- self.cats.clear()
- if not self._checkMap():
- return -1
- ndblinks = Vect_get_num_dblinks(self.poMapInfo)
- for i in range(ndblinks):
- fi = Vect_get_dblink(self.poMapInfo, i).contents
- if fi:
- self.cats[fi.number] = None
- # find max category
- nfields = Vect_cidx_get_num_fields(self.poMapInfo)
- Debug.msg(2, "wxDigit.InitCats(): nfields=%d", nfields)
- for i in range(nfields):
- field = Vect_cidx_get_field_number(self.poMapInfo, i)
- ncats = Vect_cidx_get_num_cats_by_index(self.poMapInfo, i)
- if field <= 0:
- continue
- for j in range(ncats):
- cat = c_int()
- type = c_int()
- id = c_int()
- Vect_cidx_get_cat_by_index(
- self.poMapInfo, i, j, byref(cat), byref(type), byref(id)
- )
- if field in self.cats:
- if self.cats[field] is None or cat.value > self.cats[field]:
- self.cats[field] = cat.value
- else:
- self.cats[field] = cat.value
- Debug.msg(
- 3, "wxDigit.InitCats(): layer=%d, cat=%d", field, self.cats[field]
- )
- # set default values
- for field, cat in six.iteritems(self.cats):
- if cat is None:
- self.cats[field] = 0 # first category 1
- Debug.msg(
- 3, "wxDigit.InitCats(): layer=%d, cat=%d", field, self.cats[field]
- )
- def _checkMap(self):
- """Check if map is open"""
- if not self.poMapInfo:
- self._error.NoMap()
- return False
- return True
- def _addFeature(self, ftype, coords, layer, cat, snap, threshold):
- """Add new feature(s) to the vector map
- :param ftype: feature type (GV_POINT, GV_LINE, GV_BOUNDARY, ...)
- :param coords: tuple of coordinates ((x, y), (x, y), ...)
- :param layer: layer number (-1 for no cat)
- :param cat: category number
- :param snap: snap to node/vertex
- :param threshold: threshold for snapping
- :return: tuple (number of added features, list of fids)
- :return: number of features -1 on error
- """
- fids = list()
- if not self._checkMap():
- return (-1, None)
- is3D = bool(Vect_is_3d(self.poMapInfo))
- Debug.msg(
- 2,
- "IVDigit._addFeature(): npoints=%d, layer=%d, cat=%d, snap=%d",
- len(coords),
- layer,
- cat,
- snap,
- )
- if not (ftype & (GV_POINTS | GV_LINES | GV_AREA)): # TODO: 3D
- self._error.FeatureType(ftype)
- return (-1, None)
- # set category
- Vect_reset_cats(self.poCats)
- if layer > 0 and ftype != GV_AREA:
- Vect_cat_set(self.poCats, layer, cat)
- self.cats[layer] = max(cat, self.cats.get(layer, 1))
- # append points
- Vect_reset_line(self.poPoints)
- for c in coords:
- Vect_append_point(self.poPoints, c[0], c[1], 0.0)
- if ftype & (GV_BOUNDARY | GV_AREA):
- # close boundary
- points = self.poPoints.contents
- last = points.n_points - 1
- if self._settings["closeBoundary"]:
- Vect_append_point(self.poPoints, points.x[0], points.y[0], points.z[0])
- elif (
- Vect_points_distance(
- points.x[0],
- points.y[0],
- points.z[0],
- points.x[last],
- points.y[last],
- points.z[last],
- is3D,
- )
- <= threshold
- ):
- points.x[last] = points.x[0]
- points.y[last] = points.y[0]
- points.z[last] = points.z[0]
- if snap != NO_SNAP:
- # apply snapping (node or vertex)
- modeSnap = not (snap == SNAP)
- Vedit_snap_line(
- self.poMapInfo,
- self.popoBgMapInfo,
- int(self.poBgMapInfo is not None),
- -1,
- self.poPoints,
- threshold,
- modeSnap,
- )
- if ftype == GV_AREA:
- ltype = GV_BOUNDARY
- else:
- ltype = ftype
- newline = Vect_write_line(self.poMapInfo, ltype, self.poPoints, self.poCats)
- if newline < 0:
- self._error.WriteLine()
- return (-1, None)
- fids.append(newline)
- # add centroids for left/right area
- if ftype & GV_AREA:
- left = right = -1
- bpoints = Vect_new_line_struct()
- cleft = c_int()
- cright = c_int()
- Vect_get_line_areas(self.poMapInfo, newline, byref(cleft), byref(cright))
- left = cleft.value
- right = cright.value
- Debug.msg(3, "IVDigit._addFeature(): area - left=%d right=%d", left, right)
- # check if area exists and has no centroid inside
- if layer > 0 and (left > 0 or right > 0):
- Vect_cat_set(self.poCats, layer, cat)
- self.cats[layer] = max(cat, self.cats.get(layer, 0))
- x = c_double()
- y = c_double()
- if left > 0 and Vect_get_area_centroid(self.poMapInfo, left) == 0:
- # if Vect_get_area_points(self.poMapInfo, left, bpoints) > 0 and
- # Vect_find_poly_centroid(bpoints, byref(x), byref(y)) == 0:
- if (
- Vect_get_point_in_area(self.poMapInfo, left, byref(x), byref(y))
- == 0
- ):
- Vect_reset_line(bpoints)
- Vect_append_point(bpoints, x.value, y.value, 0.0)
- newc = Vect_write_line(
- self.poMapInfo, GV_CENTROID, bpoints, self.poCats
- )
- if newc < 0:
- self._error.WriteLine()
- return (len(fids), fids)
- else:
- fids.append(newc)
- if right > 0 and Vect_get_area_centroid(self.poMapInfo, right) == 0:
- # if Vect_get_area_points(byref(self.poMapInfo), right, bpoints) > 0 and
- # Vect_find_poly_centroid(bpoints, byref(x), byref(y)) == 0:
- if (
- Vect_get_point_in_area(self.poMapInfo, right, byref(x), byref(y))
- == 0
- ):
- Vect_reset_line(bpoints)
- Vect_append_point(bpoints, x.value, y.value, 0.0)
- newc = Vect_write_line(
- self.poMapInfo, GV_CENTROID, bpoints, self.poCats
- )
- if newc < 0:
- self._error.WriteLine()
- return len(fids, fids)
- else:
- fids.append(newc)
- Vect_destroy_line_struct(bpoints)
- # break line or boundary at intersection
- if self._settings["breakLines"]:
- self._breakLineAtIntersection(newline, self.poPoints)
- self._addChangeset()
- if ftype & GV_AREA:
- # len(fids) == 1 -> no new area
- return (len(fids) - 1, fids)
- return (len(fids), fids)
- def _ModifyLineVertex(self, coords, add=True):
- """Add or remove vertex
- Shape of line/boundary is not changed when adding new vertex.
- :param coords: coordinates of point
- :param add: True to add, False to remove
- :type add: bool
- :return: 1 on success
- :return: 0 nothing changed
- :return: -1 error
- """
- if not self._checkMap():
- return -1
- selected = self._display.selected
- if len(selected["ids"]) != 1:
- return 0
- poList = self._display.GetSelectedIList()
- if self.emit_signals:
- cList = poList.contents
- old_bboxs = [self._getBbox(cList.value[0])]
- old_areas_cats = [self._getLineAreasCategories(cList.value[0])]
- Vect_set_updated(self.poMapInfo, 1)
- n_up_lines_old = Vect_get_num_updated_lines(self.poMapInfo)
- Vect_reset_line(self.poPoints)
- Vect_append_point(self.poPoints, coords[0], coords[1], 0.0)
- thresh = self._display.GetThreshold(type="selectThresh")
- if add:
- ret = Vedit_add_vertex(self.poMapInfo, poList, self.poPoints, thresh)
- else:
- ret = Vedit_remove_vertex(self.poMapInfo, poList, self.poPoints, thresh)
- Vect_destroy_list(poList)
- if ret > 0 and self.emit_signals:
- new_bboxs = []
- new_areas_cats = []
- n_up_lines = Vect_get_num_updated_lines(self.poMapInfo)
- for i in range(n_up_lines_old, n_up_lines):
- new_id = Vect_get_updated_line(self.poMapInfo, i)
- new_areas_cats.append(self._getLineAreasCategories(new_id))
- new_bboxs.append(self._getBbox(new_id))
- if not add and ret > 0 and self._settings["breakLines"]:
- self._breakLineAtIntersection(Vect_get_num_lines(self.poMapInfo), None)
- if ret > 0:
- self._addChangeset()
- if ret > 0 and self.emit_signals:
- if add:
- self.vertexAdded.emit(old_bboxs=old_bboxs, new_bboxs=new_bboxs)
- else:
- self.vertexRemoved.emit(
- old_bboxs=old_bboxs,
- new_bboxs=new_bboxs,
- old_areas_cats=old_areas_cats,
- new_areas_cats=new_areas_cats,
- )
- return 1
- def GetLineCats(self, line):
- """Get list of layer/category(ies) for selected feature.
- :param line: feature id (-1 for first selected feature)
- :return: list of layer/cats
- """
- ret = dict()
- if not self._checkMap():
- return ret
- if line == -1 and len(self._display.selected["ids"]) < 1:
- return ret
- if line == -1:
- line = self._display.selected["ids"][0]
- if not Vect_line_alive(self.poMapInfo, line):
- self._error.DeadLine(line)
- return ret
- if Vect_read_line(self.poMapInfo, None, self.poCats, line) < 0:
- self._error.ReadLine(line)
- return ret
- cats = self.poCats.contents
- for i in range(cats.n_cats):
- field = cats.field[i]
- if field not in ret:
- ret[field] = list()
- ret[field].append(cats.cat[i])
- return ret
- def GetLayers(self):
- """Get list of layers
- Requires self.InitCats() to be called.
- :return: list of layers
- """
- return self.cats.keys()
- def UpdateSettings(self):
- """Update digit (and display) settings"""
- self._display.UpdateSettings()
- self._settings["breakLines"] = bool(
- UserSettings.Get(group="vdigit", key="breakLines", subkey="enabled")
- )
- self._settings["closeBoundary"] = bool(
- UserSettings.Get(group="vdigit", key="closeBoundary", subkey="enabled")
- )
- def SetCategory(self):
- """Update self.cats based on settings"""
- sel = UserSettings.Get(group="vdigit", key="categoryMode", subkey="selection")
- cat = None
- if sel == 0: # next to usep
- cat = self._setCategoryNextToUse()
- elif sel == 1:
- cat = UserSettings.Get(group="vdigit", key="category", subkey="value")
- if cat:
- layer = UserSettings.Get(group="vdigit", key="layer", subkey="value")
- self.cats[layer] = cat
- return cat
- def _setCategoryNextToUse(self):
- """Find maximum category number for the given layer and
- update the settings
- :return: category to be used
- """
- # get max category number for given layer and update the settings
- layer = UserSettings.Get(group="vdigit", key="layer", subkey="value")
- cat = self.cats.get(layer, 0) + 1
- UserSettings.Set(group="vdigit", key="category", subkey="value", value=cat)
- Debug.msg(1, "IVDigit._setCategoryNextToUse(): cat=%d", cat)
- return cat
- def SelectLinesFromBackgroundMap(self, bbox):
- """Select features from background map
- :param bbox: bounding box definition
- :return: list of selected feature ids
- """
- # try select features by box first
- if self._display.SelectLinesByBox(bbox, poMapInfo=self.poBgMapInfo) < 1:
- self._display.SelectLineByPoint(bbox[0], poMapInfo=self.poBgMapInfo)["line"]
- return self._display.selected["ids"]
- def GetUndoLevel(self):
- """Get undo level (number of active changesets)
- Note: Changesets starts with 0
- """
- return self.changesetCurrent
- def GetFeatureType(self):
- """Get feature type for OGR layers
- :return: feature type as string (point, linestring, polygon)
- :return: None for native format
- """
- topoFormat = Vect_get_finfo_topology_info(self.poMapInfo, None, None, None)
- if topoFormat == GV_TOPO_PSEUDO:
- return Vect_get_finfo_geometry_type(self.poMapInfo)
- return ""
|