geometry.py 62 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981
  1. """
  2. Created on Wed Jul 18 10:46:25 2012
  3. @author: pietro
  4. """
  5. import ctypes
  6. import re
  7. from collections import namedtuple
  8. import numpy as np
  9. import grass.lib.gis as libgis
  10. import grass.lib.vector as libvect
  11. from grass.pygrass.utils import decode
  12. from grass.pygrass.errors import GrassError, mapinfo_must_be_set
  13. from grass.pygrass.vector.basic import Ilist, Bbox, Cats
  14. from grass.pygrass.vector import sql
  15. # For test purposes
  16. test_vector_name = "geometry_doctest_map"
  17. LineDist = namedtuple("LineDist", "point dist spdist sldist")
  18. WKT = {
  19. "POINT\((.*)\)": "point", # 'POINT\(\s*([+-]*\d+\.*\d*)+\s*\)'
  20. "LINESTRING\((.*)\)": "line",
  21. }
  22. def read_WKT(string):
  23. """Read the string and return a geometry object
  24. **WKT**:
  25. ::
  26. POINT(0 0)
  27. LINESTRING(0 0,1 1,1 2)
  28. POLYGON((0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1))
  29. MULTIPOINT(0 0,1 2)
  30. MULTILINESTRING((0 0,1 1,1 2),(2 3,3 2,5 4))
  31. MULTIPOLYGON(((0 0,4 0,4 4,0 4,0 0),(1 1,2 1,2 2,1 2,1 1)),
  32. ((-1 -1,-1 -2,-2 -2,-2 -1,-1 -1)))
  33. GEOMETRYCOLLECTION(POINT(2 3),LINESTRING(2 3,3 4))
  34. **EWKT**:
  35. ::
  36. POINT(0 0 0) -- XYZ
  37. SRID=32632;POINT(0 0) -- XY with SRID
  38. POINTM(0 0 0) -- XYM
  39. POINT(0 0 0 0) -- XYZM
  40. SRID=4326;MULTIPOINTM(0 0 0,1 2 1) -- XYM with SRID
  41. MULTILINESTRING((0 0 0,1 1 0,1 2 1),(2 3 1,3 2 1,5 4 1))
  42. POLYGON((0 0 0,4 0 0,4 4 0,0 4 0,0 0 0),(1 1 0,2 1 0,2 2 0,1 2 0,1 1 0))
  43. MULTIPOLYGON(((0 0 0,4 0 0,4 4 0,0 4 0,0 0 0),
  44. (1 1 0,2 1 0,2 2 0,1 2 0,1 1 0)),
  45. ((-1 -1 0,-1 -2 0,-2 -2 0,-2 -1 0,-1 -1 0)))
  46. GEOMETRYCOLLECTIONM( POINTM(2 3 9), LINESTRINGM(2 3 4, 3 4 5) )
  47. MULTICURVE( (0 0, 5 5), CIRCULARSTRING(4 0, 4 4, 8 4) )
  48. POLYHEDRALSURFACE( ((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)),
  49. ((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)),
  50. ((0 0 0, 1 0 0, 1 0 1, 0 0 1, 0 0 0)),
  51. ((1 1 0, 1 1 1, 1 0 1, 1 0 0, 1 1 0)),
  52. ((0 1 0, 0 1 1, 1 1 1, 1 1 0, 0 1 0)),
  53. ((0 0 1, 1 0 1, 1 1 1, 0 1 1, 0 0 1)) )
  54. TRIANGLE ((0 0, 0 9, 9 0, 0 0))
  55. TIN( ((0 0 0, 0 0 1, 0 1 0, 0 0 0)), ((0 0 0, 0 1 0, 1 1 0, 0 0 0)) )
  56. """
  57. for regexp, obj in WKT.items():
  58. if re.match(regexp, string):
  59. geo = 10
  60. return obj(geo)
  61. def read_WKB(buff):
  62. """Read the binary buffer and return a geometry object"""
  63. pass
  64. def intersects(lineA, lineB, with_z=False):
  65. """Return a list of points
  66. >>> lineA = Line([(0, 0), (4, 0)])
  67. >>> lineB = Line([(2, 2), (2, -2)])
  68. >>> intersects(lineA, lineB)
  69. Line([Point(2.000000, 0.000000)])
  70. """
  71. line = Line()
  72. if libvect.Vect_line_get_intersections(
  73. lineA.c_points, lineB.c_points, line.c_points, int(with_z)
  74. ):
  75. return line
  76. else:
  77. return []
  78. # =============================================
  79. # GEOMETRY
  80. # =============================================
  81. def get_xyz(pnt):
  82. """Return a tuple with: x, y, z.
  83. >>> pnt = Point(0, 0)
  84. >>> get_xyz(pnt)
  85. (0.0, 0.0, 0.0)
  86. >>> get_xyz((1, 1))
  87. (1, 1, 0.0)
  88. >>> get_xyz((1, 1, 2))
  89. (1, 1, 2)
  90. >>> get_xyz((1, 1, 2, 2)) #doctest: +ELLIPSIS
  91. Traceback (most recent call last):
  92. ...
  93. ValueError: The the format of the point is not supported: (1, 1, 2, 2)
  94. """
  95. if isinstance(pnt, Point):
  96. if pnt.is2D:
  97. x, y = pnt.x, pnt.y
  98. z = 0.0
  99. else:
  100. x, y, z = pnt.x, pnt.y, pnt.z
  101. else:
  102. if len(pnt) == 2:
  103. x, y = pnt
  104. z = 0.0
  105. elif len(pnt) == 3:
  106. x, y, z = pnt
  107. else:
  108. str_error = "The the format of the point is not supported: {0!r}"
  109. raise ValueError(str_error.format(pnt))
  110. return x, y, z
  111. class Attrs(object):
  112. def __init__(self, cat, table, writeable=False):
  113. self._cat = None
  114. self.cond = ""
  115. self.table = table
  116. self.cat = cat
  117. self.writeable = writeable
  118. def _get_cat(self):
  119. return self._cat
  120. def _set_cat(self, value):
  121. self._cat = value
  122. if value:
  123. # update condition
  124. self.cond = "%s=%d" % (self.table.key, value)
  125. cat = property(fget=_get_cat, fset=_set_cat, doc="Set and obtain cat value")
  126. def __getitem__(self, keys):
  127. """Return the value stored in the attribute table.
  128. >>> from grass.pygrass.vector import VectorTopo
  129. >>> test_vect = VectorTopo(test_vector_name)
  130. >>> test_vect.open('r')
  131. >>> v1 = test_vect[1]
  132. >>> v1.attrs['name']
  133. 'point'
  134. >>> v1.attrs['name', 'value']
  135. ('point', 1.0)
  136. >>> test_vect.close()
  137. """
  138. sqlcode = sql.SELECT_WHERE.format(
  139. cols=(keys if np.isscalar(keys) else ", ".join(keys)),
  140. tname=self.table.name,
  141. condition=self.cond,
  142. )
  143. cur = self.table.execute(sqlcode)
  144. results = cur.fetchone()
  145. if results is not None:
  146. return results[0] if len(results) == 1 else results
  147. def __setitem__(self, keys, values):
  148. """Set value of a given column of a table attribute.
  149. >>> from grass.pygrass.vector import VectorTopo
  150. >>> test_vect = VectorTopo(test_vector_name)
  151. >>> test_vect.open('r')
  152. >>> v1 = test_vect[1]
  153. >>> v1.attrs['name']
  154. 'point'
  155. >>> v1.attrs['name'] = "new_point_1"
  156. >>> v1.attrs['name']
  157. 'new_point_1'
  158. >>> v1.attrs['name', 'value'] = "new_point_2", 100.
  159. >>> v1.attrs['name', 'value']
  160. ('new_point_2', 100.0)
  161. >>> v1.attrs['name', 'value'] = "point", 1.
  162. >>> v1.attrs.table.conn.commit()
  163. >>> test_vect.close()
  164. """
  165. if self.writeable:
  166. if np.isscalar(keys):
  167. keys, values = (keys,), (values,)
  168. # check if key is a column of the table or not
  169. for key in keys:
  170. if key not in self.table.columns:
  171. raise KeyError("Column: %s not in table" % key)
  172. # prepare the string using as paramstyle: qmark
  173. vals = ",".join(["%s=?" % k for k in keys])
  174. # "UPDATE {tname} SET {values} WHERE {condition};"
  175. sqlcode = sql.UPDATE_WHERE.format(
  176. tname=self.table.name, values=vals, condition=self.cond
  177. )
  178. self.table.execute(sqlcode, values=values)
  179. # self.table.conn.commit()
  180. else:
  181. str_err = "You can only read the attributes if the map is in another mapset"
  182. raise GrassError(str_err)
  183. def __dict__(self):
  184. """Return a dict of the attribute table row."""
  185. dic = {}
  186. for key, val in zip(self.keys(), self.values()):
  187. dic[key] = val
  188. return dic
  189. def values(self):
  190. """Return the values of the attribute table row.
  191. >>> from grass.pygrass.vector import VectorTopo
  192. >>> test_vect = VectorTopo(test_vector_name)
  193. >>> test_vect.open('r')
  194. >>> v1 = test_vect[1]
  195. >>> v1.attrs.values()
  196. (1, 'point', 1.0)
  197. >>> test_vect.close()
  198. """
  199. # SELECT {cols} FROM {tname} WHERE {condition}
  200. cur = self.table.execute(
  201. sql.SELECT_WHERE.format(
  202. cols="*", tname=self.table.name, condition=self.cond
  203. )
  204. )
  205. return cur.fetchone()
  206. def keys(self):
  207. """Return the column name of the attribute table.
  208. >>> from grass.pygrass.vector import VectorTopo
  209. >>> test_vect = VectorTopo(test_vector_name)
  210. >>> test_vect.open('r')
  211. >>> v1 = test_vect[1]
  212. >>> v1.attrs.keys()
  213. ['cat', 'name', 'value']
  214. >>> test_vect.close()
  215. """
  216. return self.table.columns.names()
  217. def commit(self):
  218. """Save the changes"""
  219. self.table.conn.commit()
  220. class Geo(object):
  221. """
  222. Base object for different feature types
  223. """
  224. gtype = None
  225. def __init__(
  226. self,
  227. v_id=0,
  228. c_mapinfo=None,
  229. c_points=None,
  230. c_cats=None,
  231. table=None,
  232. writeable=False,
  233. is2D=True,
  234. free_points=False,
  235. free_cats=False,
  236. ):
  237. """Constructor of a geometry object
  238. :param v_id: The vector feature id
  239. :param c_mapinfo: A pointer to the vector mapinfo structure
  240. :param c_points: A pointer to a libvect.line_pnts structure, this
  241. is optional, if not set an internal structure will
  242. be allocated and free'd at object destruction
  243. :param c_cats: A pointer to a libvect.line_cats structure, this
  244. is optional, if not set an internal structure will
  245. be allocated and free'd at object destruction
  246. :param table: The attribute table to select attributes for
  247. this feature
  248. :param writeable: Not sure what this is for?
  249. :param is2D: If True this feature has two dimensions, False if
  250. this feature has three dimensions
  251. :param free_points: Set this True if the provided c_points structure
  252. should be free'd at object destruction, be aware
  253. that no other object should free them, otherwise
  254. you can expect a double free corruption segfault
  255. :param free_cats: Set this True if the provided c_cats structure
  256. should be free'd at object destruction, be aware
  257. that no other object should free them, otherwise
  258. you can expect a double free corruption segfault
  259. """
  260. self.id = v_id # vector id
  261. self.c_mapinfo = c_mapinfo
  262. self.is2D = (
  263. is2D if is2D is not None else bool(libvect.Vect_is_3d(self.c_mapinfo) != 1)
  264. )
  265. # Set True if cats and points are allocated by this object
  266. # to free the cats and points structures on destruction
  267. self._free_points = False
  268. self._free_cats = False
  269. read = False
  270. # set c_points
  271. if c_points is None:
  272. self.c_points = ctypes.pointer(libvect.line_pnts())
  273. self._free_points = True
  274. read = True
  275. else:
  276. self.c_points = c_points
  277. self._free_points = free_points
  278. # set c_cats
  279. if c_cats is None:
  280. self.c_cats = ctypes.pointer(libvect.line_cats())
  281. self._free_cats = free_cats
  282. read = True
  283. else:
  284. self.c_cats = c_cats
  285. self._free_cats = True
  286. if self.id and self.c_mapinfo is not None and read:
  287. self.read()
  288. # set the attributes as last thing to do
  289. self.attrs = None
  290. if table is not None and self.cat is not None:
  291. self.attrs = Attrs(self.cat, table, writeable)
  292. def __del__(self):
  293. """Take care of the allocated line_pnts and line_cats allocation"""
  294. if self._free_points is True and self.c_points:
  295. if self.c_points.contents.alloc_points > 0:
  296. # print("G_free(points) [%i]"%(self.c_points.contents.alloc_points))
  297. libgis.G_free(self.c_points.contents.x)
  298. libgis.G_free(self.c_points.contents.y)
  299. if self.c_points.contents.z:
  300. libgis.G_free(self.c_points.contents.z)
  301. if self._free_cats is True and self.c_cats:
  302. if self.c_cats.contents.alloc_cats > 0:
  303. # print("G_free(cats) [%i]"%(self.c_cats.contents.alloc_cats))
  304. libgis.G_free(self.c_cats.contents.cat)
  305. @property
  306. def cat(self):
  307. if self.c_cats.contents.cat:
  308. return self.c_cats.contents.cat.contents.value
  309. def has_topology(self):
  310. if self.c_mapinfo is not None:
  311. return self.c_mapinfo.contents.level == 2
  312. else:
  313. return False
  314. @mapinfo_must_be_set
  315. def read(self):
  316. """Read and set the coordinates of the centroid from the vector map,
  317. using the centroid_id and calling the Vect_read_line C function"""
  318. self.id, ftype, c_points, c_cats = c_read_line(
  319. self.id, self.c_mapinfo, self.c_points, self.c_cats
  320. )
  321. def to_wkt(self):
  322. """Return a "well know text" (WKT) geometry string, this method uses
  323. the GEOS implementation in the vector library. ::
  324. >>> pnt = Point(10, 100)
  325. >>> pnt.to_wkt()
  326. 'POINT (10.0000000000000000 100.0000000000000000)'
  327. """
  328. return decode(
  329. libvect.Vect_line_to_wkt(self.c_points, self.gtype, not self.is2D)
  330. )
  331. def to_wkb(self):
  332. """Return a "well know binary" (WKB) geometry byte array, this method uses
  333. the GEOS implementation in the vector library. ::
  334. >>> pnt = Point(10, 100)
  335. >>> wkb = pnt.to_wkb()
  336. >>> len(wkb)
  337. 21
  338. """
  339. size = ctypes.c_size_t()
  340. barray = libvect.Vect_line_to_wkb(
  341. self.c_points, self.gtype, not self.is2D, ctypes.byref(size)
  342. )
  343. return ctypes.string_at(barray, size.value)
  344. class Point(Geo):
  345. """Instantiate a Point object that could be 2 or 3D, default
  346. parameters are 0.
  347. ::
  348. >>> pnt = Point()
  349. >>> pnt.x
  350. 0.0
  351. >>> pnt.y
  352. 0.0
  353. >>> pnt.z
  354. >>> pnt.is2D
  355. True
  356. >>> pnt
  357. Point(0.000000, 0.000000)
  358. >>> pnt.z = 0
  359. >>> pnt.is2D
  360. False
  361. >>> pnt
  362. Point(0.000000, 0.000000, 0.000000)
  363. >>> print(pnt)
  364. POINT Z (0.0000000000000000 0.0000000000000000 0.0000000000000000)
  365. >>> c_points = ctypes.pointer(libvect.line_pnts())
  366. >>> c_cats = ctypes.pointer(libvect.line_cats())
  367. >>> p = Point(c_points = c_points, c_cats=c_cats)
  368. >>> del p
  369. >>> c_points = ctypes.pointer(libvect.line_pnts())
  370. >>> c_cats = ctypes.pointer(libvect.line_cats())
  371. >>> p = Point(c_points=c_points, c_cats=c_cats, free_points=True,
  372. ... free_cats=True)
  373. >>> del p
  374. ..
  375. """
  376. # geometry type
  377. gtype = libvect.GV_POINT
  378. def __init__(self, x=0, y=0, z=None, **kargs):
  379. super(Point, self).__init__(**kargs)
  380. if self.id and self.c_mapinfo:
  381. self.read()
  382. else:
  383. self.is2D = True if z is None else False
  384. z = z if z is not None else 0
  385. libvect.Vect_append_point(self.c_points, x, y, z)
  386. def _get_x(self):
  387. return self.c_points.contents.x[0]
  388. def _set_x(self, value):
  389. self.c_points.contents.x[0] = value
  390. x = property(fget=_get_x, fset=_set_x, doc="Set and obtain x coordinate")
  391. def _get_y(self):
  392. return self.c_points.contents.y[0]
  393. def _set_y(self, value):
  394. self.c_points.contents.y[0] = value
  395. y = property(fget=_get_y, fset=_set_y, doc="Set and obtain y coordinate")
  396. def _get_z(self):
  397. if self.is2D:
  398. return None
  399. return self.c_points.contents.z[0]
  400. def _set_z(self, value):
  401. if value is None:
  402. self.is2D = True
  403. self.c_points.contents.z[0] = 0
  404. else:
  405. self.c_points.contents.z[0] = value
  406. self.is2D = False
  407. z = property(fget=_get_z, fset=_set_z, doc="Set and obtain z coordinate")
  408. def __str__(self):
  409. return self.to_wkt()
  410. def __repr__(self):
  411. return "Point(%s)" % ", ".join(["%f" % coor for coor in self.coords()])
  412. def __eq__(self, pnt):
  413. """Return True if the coordinates are the same.
  414. >>> p0 = Point()
  415. >>> p1 = Point()
  416. >>> p2 = Point(1, 1)
  417. >>> p0 == p1
  418. True
  419. >>> p1 == p2
  420. False
  421. """
  422. if isinstance(pnt, Point):
  423. return pnt.coords() == self.coords()
  424. return Point(*pnt).coords() == self.coords()
  425. def __ne__(self, other):
  426. return not self == other
  427. # Restore Python 2 hashing beaviour on Python 3
  428. __hash__ = object.__hash__
  429. def coords(self):
  430. """Return a tuple with the point coordinates. ::
  431. >>> pnt = Point(10, 100)
  432. >>> pnt.coords()
  433. (10.0, 100.0)
  434. If the point is 2D return a x, y tuple. But if we change the ``z``
  435. the Point object become a 3D point, therefore the method return a
  436. x, y, z tuple. ::
  437. >>> pnt.z = 1000.
  438. >>> pnt.coords()
  439. (10.0, 100.0, 1000.0)
  440. ..
  441. """
  442. if self.is2D:
  443. return self.x, self.y
  444. else:
  445. return self.x, self.y, self.z
  446. def to_wkt_p(self):
  447. """Return a "well know text" (WKT) geometry string Python implementation. ::
  448. >>> pnt = Point(10, 100)
  449. >>> pnt.to_wkt_p()
  450. 'POINT(10.000000 100.000000)'
  451. .. warning::
  452. Only ``POINT`` (2/3D) are supported, ``POINTM`` and ``POINT`` with:
  453. ``XYZM`` are not supported yet.
  454. """
  455. return "POINT(%s)" % " ".join(["%f" % coord for coord in self.coords()])
  456. def distance(self, pnt):
  457. """Calculate distance of 2 points, using the Vect_points_distance
  458. C function, If one of the point have z == None, return the 2D distance.
  459. :param pnt: the point for calculate the distance
  460. :type pnt: a Point object or a tuple with the coordinates
  461. >>> pnt0 = Point(0, 0, 0)
  462. >>> pnt1 = Point(1, 0)
  463. >>> pnt0.distance(pnt1)
  464. 1.0
  465. >>> pnt1.z = 1
  466. >>> pnt1
  467. Point(1.000000, 0.000000, 1.000000)
  468. >>> pnt0.distance(pnt1)
  469. 1.4142135623730951
  470. """
  471. if self.is2D or pnt.is2D:
  472. return libvect.Vect_points_distance(self.x, self.y, 0, pnt.x, pnt.y, 0, 0)
  473. else:
  474. return libvect.Vect_points_distance(
  475. self.x, self.y, self.z, pnt.x, pnt.y, pnt.z, 1
  476. )
  477. def buffer(
  478. self, dist=None, dist_x=None, dist_y=None, angle=0, round_=True, tol=0.1
  479. ):
  480. """Return the buffer area around the point, using the
  481. ``Vect_point_buffer2`` C function.
  482. :param dist: the distance around the point
  483. :type dist: num
  484. :param dist_x: the distance along x
  485. :type dist_x: num
  486. :param dist_y: the distance along y
  487. :type dist_y: num
  488. :param angle: the angle between 0x and major axis
  489. :type angle: num
  490. :param round_: to make corners round
  491. :type round_: bool
  492. :param tol: fix the maximum distance between theoretical arc and
  493. output segments
  494. :type tol: float
  495. :returns: the buffer as Area object
  496. >>> pnt = Point(0, 0)
  497. >>> boundary, centroid = pnt.buffer(10)
  498. >>> boundary #doctest: +ELLIPSIS
  499. Line([Point(10.000000, 0.000000),...Point(10.000000, 0.000000)])
  500. >>> centroid
  501. Point(0.000000, 0.000000)
  502. """
  503. if dist is not None:
  504. dist_x = dist
  505. dist_y = dist
  506. elif not dist_x or not dist_y:
  507. raise TypeError("TypeError: buffer expected 1 arguments, got 0")
  508. bound = Line()
  509. p_points = ctypes.pointer(bound.c_points)
  510. libvect.Vect_point_buffer2(
  511. self.x, self.y, dist_x, dist_y, angle, int(round_), tol, p_points
  512. )
  513. return (bound, self)
  514. class Line(Geo):
  515. """Instantiate a new Line with a list of tuple, or with a list of Point. ::
  516. >>> line = Line([(0, 0), (1, 1), (2, 0), (1, -1)])
  517. >>> line #doctest: +NORMALIZE_WHITESPACE
  518. Line([Point(0.000000, 0.000000),
  519. Point(1.000000, 1.000000),
  520. Point(2.000000, 0.000000),
  521. Point(1.000000, -1.000000)])
  522. ..
  523. """
  524. # geometry type
  525. gtype = libvect.GV_LINE
  526. def __init__(self, points=None, **kargs):
  527. super(Line, self).__init__(**kargs)
  528. if points is not None:
  529. for pnt in points:
  530. self.append(pnt)
  531. def __getitem__(self, key):
  532. """Get line point of given index, slice allowed. ::
  533. >>> line = Line([(0, 0), (1, 1), (2, 2), (3, 3)])
  534. >>> line[1]
  535. Point(1.000000, 1.000000)
  536. >>> line[-1]
  537. Point(3.000000, 3.000000)
  538. >>> line[:2]
  539. [Point(0.000000, 0.000000), Point(1.000000, 1.000000)]
  540. ..
  541. """
  542. # TODO:
  543. # line[0].x = 10 is not working
  544. # pnt.c_px = ctypes.pointer(self.c_points.contents.x[indx])
  545. # pnt.c_px = ctypes.cast(id(self.c_points.contents.x[indx]),
  546. # ctypes.POINTER(ctypes.c_double))
  547. if isinstance(key, slice):
  548. # import pdb; pdb.set_trace()
  549. # Get the start, stop, and step from the slice
  550. return [
  551. Point(
  552. self.c_points.contents.x[indx],
  553. self.c_points.contents.y[indx],
  554. None if self.is2D else self.c_points.contents.z[indx],
  555. )
  556. for indx in range(*key.indices(len(self)))
  557. ]
  558. elif isinstance(key, int):
  559. if key < 0: # Handle negative indices
  560. key += self.c_points.contents.n_points
  561. if key >= self.c_points.contents.n_points:
  562. raise IndexError("Index out of range")
  563. return Point(
  564. self.c_points.contents.x[key],
  565. self.c_points.contents.y[key],
  566. None if self.is2D else self.c_points.contents.z[key],
  567. )
  568. else:
  569. raise ValueError("Invalid argument type: %r." % key)
  570. def __setitem__(self, indx, pnt):
  571. """Change the coordinate of point. ::
  572. >>> line = Line([(0, 0), (1, 1)])
  573. >>> line[0] = (2, 2)
  574. >>> line
  575. Line([Point(2.000000, 2.000000), Point(1.000000, 1.000000)])
  576. ..
  577. """
  578. x, y, z = get_xyz(pnt)
  579. self.c_points.contents.x[indx] = x
  580. self.c_points.contents.y[indx] = y
  581. self.c_points.contents.z[indx] = z
  582. def __iter__(self):
  583. """Return a Point generator of the Line"""
  584. return (self.__getitem__(i) for i in range(self.__len__()))
  585. def __len__(self):
  586. """Return the number of points of the line."""
  587. return self.c_points.contents.n_points
  588. def __str__(self):
  589. return self.to_wkt()
  590. def __repr__(self):
  591. return "Line([%s])" % ", ".join([repr(pnt) for pnt in self.__iter__()])
  592. def point_on_line(self, distance, angle=0, slope=0):
  593. """Return a Point object on line in the specified distance, using the
  594. `Vect_point_on_line` C function.
  595. Raise a ValueError If the distance exceed the Line length. ::
  596. >>> line = Line([(0, 0), (1, 1)])
  597. >>> line.point_on_line(5) #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
  598. Traceback (most recent call last):
  599. ...
  600. ValueError: The distance exceed the length of the line,
  601. that is: 1.414214
  602. >>> line.point_on_line(1)
  603. Point(0.707107, 0.707107)
  604. ..
  605. """
  606. # instantiate an empty Point object
  607. maxdist = self.length()
  608. if distance > maxdist:
  609. str_err = "The distance exceed the length of the line, that is: %f"
  610. raise ValueError(str_err % maxdist)
  611. pnt = Point(0, 0, -9999)
  612. if not libvect.Vect_point_on_line(
  613. self.c_points,
  614. distance,
  615. pnt.c_points.contents.x,
  616. pnt.c_points.contents.y,
  617. pnt.c_points.contents.z,
  618. ctypes.pointer(ctypes.c_double(angle)),
  619. ctypes.pointer(ctypes.c_double(slope)),
  620. ):
  621. raise ValueError("Vect_point_on_line give an error.")
  622. pnt.is2D = self.is2D
  623. return pnt
  624. @mapinfo_must_be_set
  625. def alive(self):
  626. """Return True if this line is alive or False if this line is
  627. dead or its index is out of range.
  628. """
  629. return bool(libvect.Vect_line_alive(self.c_mapinfo, self.id))
  630. def append(self, pnt):
  631. """Appends one point to the end of a line, using the
  632. ``Vect_append_point`` C function.
  633. :param pnt: the point to add to line
  634. :type pnt: a Point object or a tuple with the coordinates
  635. >>> line = Line()
  636. >>> line.append((10, 100))
  637. >>> line
  638. Line([Point(10.000000, 100.000000)])
  639. >>> line.append((20, 200))
  640. >>> line
  641. Line([Point(10.000000, 100.000000), Point(20.000000, 200.000000)])
  642. Like python list.
  643. """
  644. x, y, z = get_xyz(pnt)
  645. libvect.Vect_append_point(self.c_points, x, y, z)
  646. def bbox(self, bbox=None):
  647. """Return the bounding box of the line, using ``Vect_line_box``
  648. C function. ::
  649. >>> line = Line([(0, 0), (0, 1), (2, 1), (2, 0)])
  650. >>> bbox = line.bbox()
  651. >>> bbox
  652. Bbox(1.0, 0.0, 2.0, 0.0)
  653. ..
  654. """
  655. bbox = bbox if bbox else Bbox()
  656. libvect.Vect_line_box(self.c_points, bbox.c_bbox)
  657. return bbox
  658. def extend(self, line, forward=True):
  659. """Appends points to the end of a line.
  660. :param line: it is possible to extend a line, give a list of points,
  661. or directly with a line_pnts struct.
  662. :type line: Line object ot list of points
  663. :param forward: if forward is True the line is extend forward otherwise
  664. is extend backward. The method use the
  665. `Vect_append_points` C function.
  666. :type forward: bool
  667. >>> line = Line([(0, 0), (1, 1)])
  668. >>> line.extend( Line([(2, 2), (3, 3)]) )
  669. >>> line #doctest: +NORMALIZE_WHITESPACE
  670. Line([Point(0.000000, 0.000000),
  671. Point(1.000000, 1.000000),
  672. Point(2.000000, 2.000000),
  673. Point(3.000000, 3.000000)])
  674. """
  675. # set direction
  676. if forward:
  677. direction = libvect.GV_FORWARD
  678. else:
  679. direction = libvect.GV_BACKWARD
  680. # check if is a Line object
  681. if isinstance(line, Line):
  682. c_points = line.c_points
  683. else:
  684. # instantiate a Line object
  685. lin = Line()
  686. for pnt in line:
  687. # add the points to the line
  688. lin.append(pnt)
  689. c_points = lin.c_points
  690. libvect.Vect_append_points(self.c_points, c_points, direction)
  691. def insert(self, indx, pnt):
  692. """Insert new point at index position and move all old points at
  693. that position and above up, using ``Vect_line_insert_point``
  694. C function.
  695. :param indx: the index where add new point
  696. :type indx: int
  697. :param pnt: the point to add
  698. :type pnt: a Point object
  699. >>> line = Line([(0, 0), (1, 1)])
  700. >>> line.insert(0, Point(1.000000, -1.000000) )
  701. >>> line #doctest: +NORMALIZE_WHITESPACE
  702. Line([Point(1.000000, -1.000000),
  703. Point(0.000000, 0.000000),
  704. Point(1.000000, 1.000000)])
  705. """
  706. if indx < 0: # Handle negative indices
  707. indx += self.c_points.contents.n_points
  708. if indx >= self.c_points.contents.n_points:
  709. raise IndexError("Index out of range")
  710. x, y, z = get_xyz(pnt)
  711. libvect.Vect_line_insert_point(self.c_points, indx, x, y, z)
  712. def length(self):
  713. """Calculate line length, 3D-length in case of 3D vector line, using
  714. `Vect_line_length` C function. ::
  715. >>> line = Line([(0, 0), (1, 1), (0, 1)])
  716. >>> line.length()
  717. 2.414213562373095
  718. ..
  719. """
  720. return libvect.Vect_line_length(self.c_points)
  721. def length_geodesic(self):
  722. """Calculate line length, usig `Vect_line_geodesic_length` C function.
  723. ::
  724. >>> line = Line([(0, 0), (1, 1), (0, 1)])
  725. >>> line.length_geodesic()
  726. 2.414213562373095
  727. ..
  728. """
  729. return libvect.Vect_line_geodesic_length(self.c_points)
  730. def distance(self, pnt):
  731. """Calculate the distance between line and a point.
  732. :param pnt: the point to calculate distance
  733. :type pnt: a Point object or a tuple with the coordinates
  734. Return a namedtuple with:
  735. * point: the closest point on the line,
  736. * dist: the distance between these two points,
  737. * spdist: distance to point on line from segment beginning
  738. * sldist: distance to point on line form line beginning along line
  739. The distance is compute using the ``Vect_line_distance`` C function.
  740. >>> point = Point(2.3, 0.5)
  741. >>> line = Line([(0, 0), (2, 0), (3, 0)])
  742. >>> line.distance(point) #doctest: +NORMALIZE_WHITESPACE
  743. LineDist(point=Point(2.300000, 0.000000),
  744. dist=0.5, spdist=0.2999999999999998, sldist=2.3)
  745. """
  746. # instantite outputs
  747. cx = ctypes.c_double(0)
  748. cy = ctypes.c_double(0)
  749. cz = ctypes.c_double(0)
  750. dist = ctypes.c_double(0)
  751. sp_dist = ctypes.c_double(0)
  752. lp_dist = ctypes.c_double(0)
  753. libvect.Vect_line_distance(
  754. self.c_points,
  755. pnt.x,
  756. pnt.y,
  757. 0 if pnt.is2D else pnt.z,
  758. 0 if self.is2D else 1,
  759. ctypes.byref(cx),
  760. ctypes.byref(cy),
  761. ctypes.byref(cz),
  762. ctypes.byref(dist),
  763. ctypes.byref(sp_dist),
  764. ctypes.byref(lp_dist),
  765. )
  766. # instantiate the Point class
  767. point = Point(cx.value, cy.value, cz.value)
  768. point.is2D = self.is2D
  769. return LineDist(point, dist.value, sp_dist.value, lp_dist.value)
  770. @mapinfo_must_be_set
  771. def first_cat(self):
  772. """Fetches FIRST category number for given vector line and field, using
  773. the ``Vect_get_line_cat`` C function.
  774. .. warning::
  775. Not implemented yet.
  776. """
  777. # TODO: add this method.
  778. # libvect.Vect_get_line_cat(self.c_mapinfo, self.id, self.field)
  779. pass
  780. def pop(self, indx):
  781. """Return the point in the index position and remove from the Line.
  782. :param indx: the index where add new point
  783. :type indx: int
  784. >>> line = Line([(0, 0), (1, 1), (2, 2)])
  785. >>> midle_pnt = line.pop(1)
  786. >>> midle_pnt #doctest: +NORMALIZE_WHITESPACE
  787. Point(1.000000, 1.000000)
  788. >>> line #doctest: +NORMALIZE_WHITESPACE
  789. Line([Point(0.000000, 0.000000), Point(2.000000, 2.000000)])
  790. """
  791. if indx < 0: # Handle negative indices
  792. indx += self.c_points.contents.n_points
  793. if indx >= self.c_points.contents.n_points:
  794. raise IndexError("Index out of range")
  795. pnt = self.__getitem__(indx)
  796. libvect.Vect_line_delete_point(self.c_points, indx)
  797. return pnt
  798. def delete(self, indx):
  799. """Remove the point in the index position.
  800. :param indx: the index where add new point
  801. :type indx: int
  802. >>> line = Line([(0, 0), (1, 1), (2, 2)])
  803. >>> line.delete(-1)
  804. >>> line #doctest: +NORMALIZE_WHITESPACE
  805. Line([Point(0.000000, 0.000000), Point(1.000000, 1.000000)])
  806. """
  807. if indx < 0: # Handle negative indices
  808. indx += self.c_points.contents.n_points
  809. if indx >= self.c_points.contents.n_points:
  810. raise IndexError("Index out of range")
  811. libvect.Vect_line_delete_point(self.c_points, indx)
  812. def prune(self):
  813. """Remove duplicate points, i.e. zero length segments, using
  814. `Vect_line_prune` C function. ::
  815. >>> line = Line([(0, 0), (1, 1), (1, 1), (2, 2)])
  816. >>> line.prune()
  817. >>> line #doctest: +NORMALIZE_WHITESPACE
  818. Line([Point(0.000000, 0.000000),
  819. Point(1.000000, 1.000000),
  820. Point(2.000000, 2.000000)])
  821. ..
  822. """
  823. libvect.Vect_line_prune(self.c_points)
  824. def prune_thresh(self, threshold):
  825. """Remove points in threshold, using the ``Vect_line_prune_thresh``
  826. C function.
  827. :param threshold: the threshold value where prune points
  828. :type threshold: num
  829. >>> line = Line([(0, 0), (1.0, 1.0), (1.2, 0.9), (2, 2)])
  830. >>> line.prune_thresh(0.5)
  831. >>> line #doctest: +SKIP +NORMALIZE_WHITESPACE
  832. Line([Point(0.000000, 0.000000),
  833. Point(1.000000, 1.000000),
  834. Point(2.000000, 2.000000)])
  835. .. warning ::
  836. prune_thresh is not working yet.
  837. """
  838. libvect.Vect_line_prune(self.c_points, ctypes.c_double(threshold))
  839. def remove(self, pnt):
  840. """Delete point at given index and move all points above down, using
  841. `Vect_line_delete_point` C function.
  842. :param pnt: the point to remove
  843. :type pnt: a Point object or a tuple with the coordinates
  844. >>> line = Line([(0, 0), (1, 1), (2, 2)])
  845. >>> line.remove((2, 2))
  846. >>> line[-1] #doctest: +NORMALIZE_WHITESPACE
  847. Point(1.000000, 1.000000)
  848. ..
  849. """
  850. for indx, point in enumerate(self.__iter__()):
  851. if pnt == point:
  852. libvect.Vect_line_delete_point(self.c_points, indx)
  853. return
  854. raise ValueError("list.remove(x): x not in list")
  855. def reverse(self):
  856. """Reverse the order of vertices, using `Vect_line_reverse`
  857. C function. ::
  858. >>> line = Line([(0, 0), (1, 1), (2, 2)])
  859. >>> line.reverse()
  860. >>> line #doctest: +NORMALIZE_WHITESPACE
  861. Line([Point(2.000000, 2.000000),
  862. Point(1.000000, 1.000000),
  863. Point(0.000000, 0.000000)])
  864. ..
  865. """
  866. libvect.Vect_line_reverse(self.c_points)
  867. def segment(self, start, end):
  868. """Create line segment. using the ``Vect_line_segment`` C function.
  869. :param start: distance from the beginning of the line where
  870. the segment start
  871. :type start: float
  872. :param end: distance from the beginning of the line where
  873. the segment end
  874. :type end: float
  875. ::
  876. # x (1, 1)
  877. # |
  878. # |-
  879. # |
  880. # x--------x (1, 0)
  881. # (0, 0) ^
  882. >>> line = Line([(0, 0), (1, 0), (1, 1)])
  883. >>> line.segment(0.5, 1.5) #doctest: +NORMALIZE_WHITESPACE
  884. Line([Point(0.500000, 0.000000),
  885. Point(1.000000, 0.000000),
  886. Point(1.000000, 0.500000)])
  887. """
  888. line = Line()
  889. libvect.Vect_line_segment(self.c_points, start, end, line.c_points)
  890. return line
  891. def to_list(self):
  892. """Return a list of tuple. ::
  893. >>> line = Line([(0, 0), (1, 1), (2, 0), (1, -1)])
  894. >>> line.to_list()
  895. [(0.0, 0.0), (1.0, 1.0), (2.0, 0.0), (1.0, -1.0)]
  896. ..
  897. """
  898. return [pnt.coords() for pnt in self.__iter__()]
  899. def to_array(self):
  900. """Return an array of coordinates. ::
  901. >>> line = Line([(0, 0), (1, 1), (2, 0), (1, -1)])
  902. >>> line.to_array() #doctest: +NORMALIZE_WHITESPACE
  903. array([[ 0., 0.],
  904. [ 1., 1.],
  905. [ 2., 0.],
  906. [ 1., -1.]])
  907. ..
  908. """
  909. return np.array(self.to_list())
  910. def to_wkt_p(self):
  911. """Return a Well Known Text string of the line. ::
  912. >>> line = Line([(0, 0), (1, 1), (1, 2)])
  913. >>> line.to_wkt_p() #doctest: +ELLIPSIS
  914. 'LINESTRING(0.000000 0.000000, ..., 1.000000 2.000000)'
  915. ..
  916. """
  917. return "LINESTRING(%s)" % ", ".join(
  918. [
  919. " ".join(["%f" % coord for coord in pnt.coords()])
  920. for pnt in self.__iter__()
  921. ]
  922. )
  923. def from_wkt(self, wkt):
  924. """Create a line reading a WKT string.
  925. :param wkt: the WKT string containing the LINESTRING
  926. :type wkt: str
  927. >>> line = Line()
  928. >>> line.from_wkt("LINESTRING(0 0,1 1,1 2)")
  929. >>> line #doctest: +NORMALIZE_WHITESPACE
  930. Line([Point(0.000000, 0.000000),
  931. Point(1.000000, 1.000000),
  932. Point(1.000000, 2.000000)])
  933. ..
  934. """
  935. match = re.match("LINESTRING\((.*)\)", wkt)
  936. if match:
  937. self.reset()
  938. for coord in match.groups()[0].strip().split(","):
  939. self.append(tuple([float(e) for e in coord.split(" ")]))
  940. else:
  941. return None
  942. def buffer(
  943. self,
  944. dist=None,
  945. dist_x=None,
  946. dist_y=None,
  947. angle=0,
  948. round_=True,
  949. caps=True,
  950. tol=0.1,
  951. ):
  952. """Return the buffer area around the line, using the
  953. ``Vect_line_buffer2`` C function.
  954. :param dist: the distance around the line
  955. :type dist: num
  956. :param dist_x: the distance along x
  957. :type dist_x: num
  958. :param dist_y: the distance along y
  959. :type dist_y: num
  960. :param angle: the angle between 0x and major axis
  961. :type angle: num
  962. :param round_: to make corners round
  963. :type round_: bool
  964. :param tol: fix the maximum distance between theoretical arc and
  965. output segments
  966. :type tol: float
  967. :returns: the buffer as Area object
  968. >>> line = Line([(0, 0), (0, 2)])
  969. >>> boundary, centroid, isles = line.buffer(10)
  970. >>> boundary #doctest: +ELLIPSIS
  971. Line([Point(-10.000000, 0.000000),...Point(-10.000000, 0.000000)])
  972. >>> centroid #doctest: +NORMALIZE_WHITESPACE
  973. Point(0.000000, 0.000000)
  974. >>> isles
  975. []
  976. ..
  977. """
  978. if dist is not None:
  979. dist_x = dist
  980. dist_y = dist
  981. elif not dist_x or not dist_y:
  982. raise TypeError("TypeError: buffer expected 1 arguments, got 0")
  983. p_bound = ctypes.pointer(ctypes.pointer(libvect.line_pnts()))
  984. pp_isle = ctypes.pointer(ctypes.pointer(ctypes.pointer(libvect.line_pnts())))
  985. n_isles = ctypes.pointer(ctypes.c_int())
  986. libvect.Vect_line_buffer2(
  987. self.c_points,
  988. dist_x,
  989. dist_y,
  990. angle,
  991. int(round_),
  992. int(caps),
  993. tol,
  994. p_bound,
  995. pp_isle,
  996. n_isles,
  997. )
  998. boundary = Line(c_points=p_bound.contents)
  999. isles = [
  1000. Line(c_points=pp_isle[i].contents)
  1001. for i in range(n_isles.contents.value)
  1002. if pp_isle[i]
  1003. ]
  1004. return (boundary, self[0], isles)
  1005. def reset(self):
  1006. """Reset line, using `Vect_reset_line` C function. ::
  1007. >>> line = Line([(0, 0), (1, 1), (2, 0), (1, -1)])
  1008. >>> len(line)
  1009. 4
  1010. >>> line.reset()
  1011. >>> len(line)
  1012. 0
  1013. >>> line
  1014. Line([])
  1015. ..
  1016. """
  1017. libvect.Vect_reset_line(self.c_points)
  1018. @mapinfo_must_be_set
  1019. def nodes(self):
  1020. """Return the start and end nodes of the line
  1021. This method requires topology build.
  1022. return: A tuple of Node objects that represent the
  1023. start and end point of this line.
  1024. """
  1025. if self.has_topology():
  1026. n1 = ctypes.c_int()
  1027. n2 = ctypes.c_int()
  1028. libvect.Vect_get_line_nodes(
  1029. self.c_mapinfo, self.id, ctypes.byref(n1), ctypes.byref(n2)
  1030. )
  1031. return (Node(n1.value, self.c_mapinfo), Node(n2.value, self.c_mapinfo))
  1032. class Node(object):
  1033. """Node class for topological analysis of line neighbors.
  1034. Objects of this class will be returned by the node() function
  1035. of a Line object.
  1036. All methods in this class require a proper setup of the Node
  1037. objects. Hence, the correct id and a valid pointer to a mapinfo
  1038. object must be provided in the constructions. Otherwise a segfault
  1039. may happen.
  1040. """
  1041. def __init__(self, v_id, c_mapinfo, **kwords):
  1042. """Construct a Node object
  1043. param v_id: The unique node id
  1044. param c_mapinfo: A valid pointer to the mapinfo object
  1045. param **kwords: Ignored
  1046. """
  1047. self.id = v_id # vector id
  1048. self.c_mapinfo = c_mapinfo
  1049. self._setup()
  1050. @mapinfo_must_be_set
  1051. def _setup(self):
  1052. self.is2D = bool(libvect.Vect_is_3d(self.c_mapinfo) != 1)
  1053. self.nlines = libvect.Vect_get_node_n_lines(self.c_mapinfo, self.id)
  1054. def __len__(self):
  1055. return self.nlines
  1056. def __iter__(self):
  1057. return self.ilines()
  1058. def __repr__(self):
  1059. return "Node(%d)" % self.id
  1060. @mapinfo_must_be_set
  1061. def alive(self):
  1062. """Return True if this node is alive or False if this node is
  1063. dead or its index is out of range.
  1064. """
  1065. return bool(libvect.Vect_node_alive(self.c_mapinfo, self.id))
  1066. @mapinfo_must_be_set
  1067. def coords(self):
  1068. """Return a tuple with the node coordinates."""
  1069. x = ctypes.c_double()
  1070. y = ctypes.c_double()
  1071. z = ctypes.c_double()
  1072. libvect.Vect_get_node_coor(
  1073. self.c_mapinfo, self.id, ctypes.byref(x), ctypes.byref(y), ctypes.byref(z)
  1074. )
  1075. return (x.value, y.value) if self.is2D else (x.value, y.value, z.value)
  1076. def to_wkt(self):
  1077. """Return a "well know text" (WKT) geometry string. ::"""
  1078. return "POINT(%s)" % " ".join(["%f" % coord for coord in self.coords()])
  1079. def to_wkb(self):
  1080. """Return a "well know binary" (WKB) geometry array. ::
  1081. TODO: Must be implemented
  1082. """
  1083. raise Exception("Not implemented")
  1084. def ilines(self, only_in=False, only_out=False):
  1085. """Return a generator with all lines id connected to a node.
  1086. The line id is negative if line is ending on the node and positive if
  1087. starting from the node.
  1088. :param only_in: Return only the lines that are ending in the node
  1089. :type only_in: bool
  1090. :param only_out: Return only the lines that are starting in the node
  1091. :type only_out: bool
  1092. """
  1093. for iline in range(self.nlines):
  1094. lid = libvect.Vect_get_node_line(self.c_mapinfo, self.id, iline)
  1095. if (not only_in and lid > 0) or (not only_out and lid < 0):
  1096. yield lid
  1097. @mapinfo_must_be_set
  1098. def lines(self, only_in=False, only_out=False):
  1099. """Return a generator with all lines connected to a node.
  1100. :param only_in: Return only the lines that are ending in the node
  1101. :type only_in: bool
  1102. :param only_out: Return only the lines that are starting in the node
  1103. :type only_out: bool
  1104. """
  1105. for iline in self.ilines(only_in, only_out):
  1106. yield Line(v_id=abs(iline), c_mapinfo=self.c_mapinfo)
  1107. @mapinfo_must_be_set
  1108. def angles(self):
  1109. """Return a generator with all lines angles in a node."""
  1110. for iline in range(self.nlines):
  1111. yield libvect.Vect_get_node_line_angle(self.c_mapinfo, self.id, iline)
  1112. class Boundary(Line):
  1113. # geometry type
  1114. gtype = libvect.GV_BOUNDARY
  1115. def __init__(self, **kargs):
  1116. super(Boundary, self).__init__(**kargs)
  1117. v_id = kargs.get("v_id", 0)
  1118. # not sure what it means that v_id is None
  1119. v_id = 0 if v_id is None else v_id
  1120. self.dir = libvect.GV_FORWARD if v_id > 0 else libvect.GV_BACKWARD
  1121. self.c_left = ctypes.pointer(ctypes.c_int())
  1122. self.c_right = ctypes.pointer(ctypes.c_int())
  1123. @property
  1124. def left_area_id(self):
  1125. """Left side area id, only available after read_area_ids() was called"""
  1126. return self.c_left.contents.value
  1127. @property
  1128. def right_area_id(self):
  1129. """Right side area id, only available after read_area_ids() was called"""
  1130. return self.c_right.contents.value
  1131. def __repr__(self):
  1132. return "Boundary([%s])" % ", ".join([repr(pnt) for pnt in self.__iter__()])
  1133. @mapinfo_must_be_set
  1134. def _centroid(self, side, idonly=False):
  1135. if side > 0:
  1136. v_id = libvect.Vect_get_area_centroid(self.c_mapinfo, side)
  1137. v_id = v_id if v_id else None
  1138. if idonly:
  1139. return v_id
  1140. else:
  1141. cntr = Centroid(v_id=v_id, c_mapinfo=self.c_mapinfo)
  1142. return cntr
  1143. def left_centroid(self, idonly=False):
  1144. """Return left centroid
  1145. :param idonly: True to return only the cat of feature
  1146. :type idonly: bool
  1147. """
  1148. return self._centroid(self.c_left.contents.value, idonly)
  1149. def right_centroid(self, idonly=False):
  1150. """Return right centroid
  1151. :param idonly: True to return only the cat of feature
  1152. :type idonly: bool
  1153. """
  1154. return self._centroid(self.c_right.contents.value, idonly)
  1155. @mapinfo_must_be_set
  1156. def read_area_ids(self):
  1157. """Read and return left and right area ids of the boundary"""
  1158. libvect.Vect_get_line_areas(self.c_mapinfo, self.id, self.c_left, self.c_right)
  1159. return self.c_left.contents.value, self.c_right.contents.value
  1160. def area(self):
  1161. """Return the area of the polygon.
  1162. >>> bound = Boundary(points=[(0, 0), (0, 2), (2, 2), (2, 0),
  1163. ... (0, 0)])
  1164. >>> bound.area()
  1165. 4.0
  1166. """
  1167. libgis.G_begin_polygon_area_calculations()
  1168. return libgis.G_area_of_polygon(
  1169. self.c_points.contents.x,
  1170. self.c_points.contents.y,
  1171. self.c_points.contents.n_points,
  1172. )
  1173. class Centroid(Point):
  1174. """The Centroid class inherit from the Point class.
  1175. Centroid contains an attribute with the C Map_info struct, and attributes
  1176. with the id of the Area. ::
  1177. >>> centroid = Centroid(x=0, y=10)
  1178. >>> centroid
  1179. Centroid(0.000000, 10.000000)
  1180. >>> from grass.pygrass.vector import VectorTopo
  1181. >>> test_vect = VectorTopo(test_vector_name)
  1182. >>> test_vect.open(mode='r')
  1183. >>> centroid = Centroid(v_id=18, c_mapinfo=test_vect.c_mapinfo)
  1184. >>> centroid
  1185. Centroid(3.500000, 3.500000)
  1186. >>> test_vect.close()
  1187. ..
  1188. """
  1189. # geometry type
  1190. gtype = libvect.GV_CENTROID
  1191. def __init__(self, area_id=None, **kargs):
  1192. super(Centroid, self).__init__(**kargs)
  1193. self.area_id = area_id
  1194. if self.id and self.c_mapinfo and self.area_id is None:
  1195. self.area_id = self._area_id()
  1196. elif self.c_mapinfo and self.area_id and self.id is None:
  1197. self.id = self._centroid_id()
  1198. if self.area_id is not None:
  1199. self.read()
  1200. # self.c_pline = ctypes.pointer(libvect.P_line()) if topology else None
  1201. def __repr__(self):
  1202. return "Centroid(%s)" % ", ".join(["%f" % co for co in self.coords()])
  1203. @mapinfo_must_be_set
  1204. def _centroid_id(self):
  1205. """Return the centroid_id, using the c_mapinfo and an area_id
  1206. attributes of the class, and calling the Vect_get_area_centroid
  1207. C function, if no centroid_id were found return None"""
  1208. centroid_id = libvect.Vect_get_area_centroid(self.c_mapinfo, self.area_id)
  1209. return centroid_id if centroid_id != 0 else None
  1210. @mapinfo_must_be_set
  1211. def _area_id(self):
  1212. """Return the area_id, using the c_mapinfo and an centroid_id
  1213. attributes of the class, and calling the Vect_centroid_area
  1214. C function, if no area_id were found return None"""
  1215. area_id = libvect.Vect_get_centroid_area(self.c_mapinfo, self.id)
  1216. return area_id if area_id != 0 else None
  1217. class Isle(Geo):
  1218. """An Isle is an area contained by another area."""
  1219. def __init__(self, **kargs):
  1220. super(Isle, self).__init__(**kargs)
  1221. # self.area_id = area_id
  1222. def __repr__(self):
  1223. return "Isle(%d)" % (self.id)
  1224. @mapinfo_must_be_set
  1225. def boundaries(self):
  1226. """Return a list of boundaries"""
  1227. ilist = Ilist()
  1228. libvect.Vect_get_isle_boundaries(self.c_mapinfo, self.id, ilist.c_ilist)
  1229. return ilist
  1230. @mapinfo_must_be_set
  1231. def bbox(self, bbox=None):
  1232. """Return bounding box of Isle"""
  1233. bbox = bbox if bbox else Bbox()
  1234. libvect.Vect_get_isle_box(self.c_mapinfo, self.id, bbox.c_bbox)
  1235. return bbox
  1236. @mapinfo_must_be_set
  1237. def points(self):
  1238. """Return a Line object with the outer ring points"""
  1239. line = Line()
  1240. libvect.Vect_get_isle_points(self.c_mapinfo, self.id, line.c_points)
  1241. return line
  1242. def to_wkt(self):
  1243. """Return a Well Known Text string of the isle. ::
  1244. For now the outer ring is returned
  1245. TODO: Implement inner rings detected from isles
  1246. """
  1247. line = self.points()
  1248. return "Polygon((%s))" % ", ".join(
  1249. [" ".join(["%f" % coord for coord in pnt]) for pnt in line.to_list()]
  1250. )
  1251. def to_wkb(self):
  1252. """Return a "well know text" (WKB) geometry array. ::"""
  1253. raise Exception("Not implemented")
  1254. @mapinfo_must_be_set
  1255. def points_geos(self):
  1256. """Return a Line object with the outer ring points"""
  1257. return libvect.Vect_get_isle_points_geos(self.c_mapinfo, self.id)
  1258. @mapinfo_must_be_set
  1259. def area_id(self):
  1260. """Returns area id for isle."""
  1261. return libvect.Vect_get_isle_area(self.c_mapinfo, self.id)
  1262. @mapinfo_must_be_set
  1263. def alive(self):
  1264. """Check if isle is alive or dead (topology required)"""
  1265. return bool(libvect.Vect_isle_alive(self.c_mapinfo, self.id))
  1266. @mapinfo_must_be_set
  1267. def contain_pnt(self, pnt):
  1268. """Check if point is in area.
  1269. :param pnt: the point to remove
  1270. :type pnt: a Point object or a tuple with the coordinates
  1271. """
  1272. bbox = self.bbox()
  1273. return bool(
  1274. libvect.Vect_point_in_island(
  1275. pnt.x, pnt.y, self.c_mapinfo, self.id, bbox.c_bbox.contents
  1276. )
  1277. )
  1278. def area(self):
  1279. """Return the area value of an Isle"""
  1280. border = self.points()
  1281. return libgis.G_area_of_polygon(
  1282. border.c_points.contents.x,
  1283. border.c_points.contents.y,
  1284. border.c_points.contents.n_points,
  1285. )
  1286. def perimeter(self):
  1287. """Return the perimeter value of an Isle."""
  1288. border = self.points()
  1289. return libvect.Vect_line_geodesic_length(border.c_points)
  1290. class Isles(object):
  1291. def __init__(self, c_mapinfo, area_id=None):
  1292. self.c_mapinfo = c_mapinfo
  1293. self.area_id = area_id
  1294. self._isles_id = None
  1295. self._isles = None
  1296. if area_id:
  1297. self._isles_id = self.isles_ids()
  1298. self._isles = self.isles()
  1299. @mapinfo_must_be_set
  1300. def __len__(self):
  1301. return libvect.Vect_get_area_num_isles(self.c_mapinfo, self.area_id)
  1302. def __repr__(self):
  1303. return "Isles(%r)" % self.area_id
  1304. def __getitem__(self, key):
  1305. if self._isles is None:
  1306. self.isles()
  1307. return self._isles[key]
  1308. @mapinfo_must_be_set
  1309. def isles_ids(self):
  1310. """Return the id of isles"""
  1311. return [
  1312. libvect.Vect_get_area_isle(self.c_mapinfo, self.area_id, i)
  1313. for i in range(self.__len__())
  1314. ]
  1315. @mapinfo_must_be_set
  1316. def isles(self):
  1317. """Return isles"""
  1318. return [
  1319. Isle(v_id=isle_id, c_mapinfo=self.c_mapinfo) for isle_id in self._isles_id
  1320. ]
  1321. class Area(Geo):
  1322. """
  1323. Vect_build_line_area,
  1324. Vect_find_area,
  1325. Vect_get_area_box,
  1326. Vect_get_area_points_geos,
  1327. Vect_centroid_area,
  1328. Vect_get_isle_area,
  1329. Vect_get_line_areas,
  1330. Vect_get_num_areas,
  1331. Vect_get_point_in_area,
  1332. Vect_isle_find_area,
  1333. Vect_point_in_area,
  1334. Vect_point_in_area_outer_ring,
  1335. Vect_read_area_geos,
  1336. Vect_remove_small_areas,
  1337. Vect_select_areas_by_box,
  1338. Vect_select_areas_by_polygon
  1339. """
  1340. # geometry type
  1341. gtype = libvect.GV_AREA
  1342. def __init__(self, **kargs):
  1343. super(Area, self).__init__(**kargs)
  1344. # set the attributes
  1345. # if self.attrs and self.cat:
  1346. # self.attrs.cat = self.cat
  1347. def __repr__(self):
  1348. return "Area(%d)" % self.id if self.id else "Area( )"
  1349. @property
  1350. def cat(self):
  1351. centroid = self.centroid()
  1352. return centroid.cat if centroid else None
  1353. @mapinfo_must_be_set
  1354. def points(self, line=None):
  1355. """Return a Line object with the outer ring
  1356. :param line: a Line object to fill with info from points of area
  1357. :type line: a Line object
  1358. """
  1359. line = Line() if line is None else line
  1360. libvect.Vect_get_area_points(self.c_mapinfo, self.id, line.c_points)
  1361. return line
  1362. @mapinfo_must_be_set
  1363. def centroid(self):
  1364. """Return the centroid
  1365. :param centroid: a Centroid object to fill with info from centroid of area
  1366. :type centroid: a Centroid object
  1367. """
  1368. centroid_id = libvect.Vect_get_area_centroid(self.c_mapinfo, self.id)
  1369. if centroid_id:
  1370. return Centroid(v_id=centroid_id, c_mapinfo=self.c_mapinfo, area_id=self.id)
  1371. @mapinfo_must_be_set
  1372. def num_isles(self):
  1373. return libvect.Vect_get_area_num_isles(self.c_mapinfo, self.id)
  1374. @mapinfo_must_be_set
  1375. def isles(self, isles=None):
  1376. """Return a list of islands located in this area"""
  1377. if isles is not None:
  1378. isles.area_id = self.id
  1379. return isles
  1380. return Isles(self.c_mapinfo, self.id)
  1381. @mapinfo_must_be_set
  1382. def area(self):
  1383. """Returns area of area without areas of isles.
  1384. double Vect_get_area_area (const struct Map_info \*Map, int area)
  1385. """
  1386. return libvect.Vect_get_area_area(self.c_mapinfo, self.id)
  1387. @mapinfo_must_be_set
  1388. def alive(self):
  1389. """Check if area is alive or dead (topology required)"""
  1390. return bool(libvect.Vect_area_alive(self.c_mapinfo, self.id))
  1391. @mapinfo_must_be_set
  1392. def bbox(self, bbox=None):
  1393. """Return the Bbox of area
  1394. :param bbox: a Bbox object to fill with info from bounding box of area
  1395. :type bbox: a Bbox object
  1396. """
  1397. bbox = bbox if bbox else Bbox()
  1398. libvect.Vect_get_area_box(self.c_mapinfo, self.id, bbox.c_bbox)
  1399. return bbox
  1400. @mapinfo_must_be_set
  1401. def buffer(
  1402. self,
  1403. dist=None,
  1404. dist_x=None,
  1405. dist_y=None,
  1406. angle=0,
  1407. round_=True,
  1408. caps=True,
  1409. tol=0.1,
  1410. ):
  1411. """Return the buffer area around the area, using the
  1412. ``Vect_area_buffer2`` C function.
  1413. :param dist: the distance around the area
  1414. :type dist: num
  1415. :param dist_x: the distance along x
  1416. :type dist_x: num
  1417. :param dist_y: the distance along y
  1418. :type dist_y: num
  1419. :param angle: the angle between 0x and major axis
  1420. :type angle: num
  1421. :param round_: to make corners round
  1422. :type round_: bool
  1423. :param tol: fix the maximum distance between theoretical arc and
  1424. output segments
  1425. :type tol: float
  1426. :returns: the buffer as line, centroid, isles object tuple
  1427. """
  1428. if dist is not None:
  1429. dist_x = dist
  1430. dist_y = dist
  1431. elif not dist_x or not dist_y:
  1432. raise TypeError("TypeError: buffer expected 1 arguments, got 0")
  1433. p_bound = ctypes.pointer(ctypes.pointer(libvect.line_pnts()))
  1434. pp_isle = ctypes.pointer(ctypes.pointer(ctypes.pointer(libvect.line_pnts())))
  1435. n_isles = ctypes.pointer(ctypes.c_int())
  1436. libvect.Vect_area_buffer2(
  1437. self.c_mapinfo,
  1438. self.id,
  1439. dist_x,
  1440. dist_y,
  1441. angle,
  1442. int(round_),
  1443. int(caps),
  1444. tol,
  1445. p_bound,
  1446. pp_isle,
  1447. n_isles,
  1448. )
  1449. return (
  1450. Line(c_points=p_bound.contents),
  1451. self.centroid(),
  1452. [Line(c_points=pp_isle[i].contents) for i in range(n_isles.contents.value)],
  1453. )
  1454. @mapinfo_must_be_set
  1455. def boundaries(self, ilist=False):
  1456. """Creates list of boundaries for given area.
  1457. int Vect_get_area_boundaries(const struct Map_info \*Map,
  1458. int area, struct ilist \*List)
  1459. """
  1460. ilst = Ilist()
  1461. libvect.Vect_get_area_boundaries(self.c_mapinfo, self.id, ilst.c_ilist)
  1462. if ilist:
  1463. return ilist
  1464. return [Boundary(v_id=abs(v_id), c_mapinfo=self.c_mapinfo) for v_id in ilst]
  1465. def to_wkt(self):
  1466. """Return a "well know text" (WKT) area string, this method uses
  1467. the GEOS implementation in the vector library. ::
  1468. """
  1469. return decode(libvect.Vect_read_area_to_wkt(self.c_mapinfo, self.id))
  1470. def to_wkb(self):
  1471. """Return a "well know binary" (WKB) area byte array, this method uses
  1472. the GEOS implementation in the vector library. ::
  1473. """
  1474. size = ctypes.c_size_t()
  1475. barray = libvect.Vect_read_area_to_wkb(
  1476. self.c_mapinfo, self.id, ctypes.byref(size)
  1477. )
  1478. return ctypes.string_at(barray, size.value)
  1479. @mapinfo_must_be_set
  1480. def cats(self, cats=None):
  1481. """Get area categories.
  1482. :param cats: a Cats object to fill with info with area categories
  1483. :type cats: a Cats object
  1484. """
  1485. cats = cats if cats else Cats()
  1486. libvect.Vect_get_area_cats(self.c_mapinfo, self.id, cats.c_cats)
  1487. return cats
  1488. def get_first_cat(self):
  1489. """Find FIRST category of given field and area.
  1490. int Vect_get_area_cat(const struct Map_info \*Map, int area, int field)
  1491. ..warning: Not implemented
  1492. """
  1493. pass
  1494. @mapinfo_must_be_set
  1495. def contains_point(self, point, bbox=None):
  1496. """Check if point is in area.
  1497. :param point: the point to analyze
  1498. :type point: a Point object or a tuple with the coordinates
  1499. :param bbox: the bounding box where run the analysis
  1500. :type bbox: a Bbox object
  1501. """
  1502. bbox = bbox if bbox else self.bbox()
  1503. return bool(
  1504. libvect.Vect_point_in_area(
  1505. point.x, point.y, self.c_mapinfo, self.id, bbox.c_bbox
  1506. )
  1507. )
  1508. @mapinfo_must_be_set
  1509. def perimeter(self):
  1510. """Calculate area perimeter.
  1511. :return: double Vect_area_perimeter (const struct line_pnts \*Points)
  1512. """
  1513. border = self.points()
  1514. return libvect.Vect_line_geodesic_length(border.c_points)
  1515. def read(self):
  1516. pass
  1517. #
  1518. # Define a dictionary to convert the feature type to name and or object
  1519. #
  1520. GV_TYPE = {
  1521. libvect.GV_POINT: {"label": "point", "obj": Point},
  1522. libvect.GV_LINE: {"label": "line", "obj": Line},
  1523. libvect.GV_BOUNDARY: {"label": "boundary", "obj": Boundary},
  1524. libvect.GV_CENTROID: {"label": "centroid", "obj": Centroid},
  1525. libvect.GV_FACE: {"label": "face", "obj": None},
  1526. libvect.GV_KERNEL: {"label": "kernel", "obj": None},
  1527. libvect.GV_AREA: {"label": "area", "obj": Area},
  1528. libvect.GV_VOLUME: {"label": "volume", "obj": None},
  1529. }
  1530. GEOOBJ = {
  1531. "areas": Area,
  1532. "dblinks": None,
  1533. "faces": None,
  1534. "holes": None,
  1535. "boundaries": Boundary,
  1536. "islands": Isle,
  1537. "kernels": None,
  1538. "line_points": None,
  1539. "points": Point,
  1540. "lines": Line,
  1541. "nodes": Node,
  1542. "volumes": None,
  1543. }
  1544. def c_read_next_line(c_mapinfo, c_points, c_cats):
  1545. v_id = c_mapinfo.contents.next_line
  1546. v_id = v_id if v_id != 0 else None
  1547. ftype = libvect.Vect_read_next_line(c_mapinfo, c_points, c_cats)
  1548. if ftype == -2:
  1549. raise StopIteration()
  1550. if ftype == -1:
  1551. raise
  1552. return ftype, v_id, c_points, c_cats
  1553. def read_next_line(
  1554. c_mapinfo, table=None, writeable=False, c_points=None, c_cats=None, is2D=True
  1555. ):
  1556. """Return the next geometry feature of a vector map."""
  1557. # Take care of good memory management
  1558. free_points = False
  1559. if c_points is None:
  1560. free_points = True
  1561. free_cats = False
  1562. if c_cats is None:
  1563. free_cats = True
  1564. c_points = c_points if c_points else ctypes.pointer(libvect.line_pnts())
  1565. c_cats = c_cats if c_cats else ctypes.pointer(libvect.line_cats())
  1566. ftype, v_id, c_points, c_cats = c_read_next_line(c_mapinfo, c_points, c_cats)
  1567. return GV_TYPE[ftype]["obj"](
  1568. v_id=v_id,
  1569. c_mapinfo=c_mapinfo,
  1570. c_points=c_points,
  1571. c_cats=c_cats,
  1572. table=table,
  1573. writeable=writeable,
  1574. is2D=is2D,
  1575. free_points=free_points,
  1576. free_cats=free_cats,
  1577. )
  1578. def c_read_line(feature_id, c_mapinfo, c_points, c_cats):
  1579. nmax = libvect.Vect_get_num_lines(c_mapinfo)
  1580. if feature_id < 0: # Handle negative indices
  1581. feature_id += nmax + 1
  1582. if feature_id > nmax:
  1583. raise IndexError("Index out of range")
  1584. if feature_id > 0:
  1585. ftype = libvect.Vect_read_line(c_mapinfo, c_points, c_cats, feature_id)
  1586. return feature_id, ftype, c_points, c_cats
  1587. else:
  1588. raise ValueError("The index must be >0, %r given." % feature_id)
  1589. def read_line(
  1590. feature_id,
  1591. c_mapinfo,
  1592. table=None,
  1593. writeable=False,
  1594. c_points=None,
  1595. c_cats=None,
  1596. is2D=True,
  1597. ):
  1598. """Return a geometry object given the feature id and the c_mapinfo."""
  1599. # Take care of good memory management
  1600. free_points = False
  1601. if c_points is None:
  1602. free_points = True
  1603. free_cats = False
  1604. if c_cats is None:
  1605. free_cats = True
  1606. c_points = c_points if c_points else ctypes.pointer(libvect.line_pnts())
  1607. c_cats = c_cats if c_cats else ctypes.pointer(libvect.line_cats())
  1608. feature_id, ftype, c_points, c_cats = c_read_line(
  1609. feature_id, c_mapinfo, c_points, c_cats
  1610. )
  1611. if GV_TYPE[ftype]["obj"] is not None:
  1612. return GV_TYPE[ftype]["obj"](
  1613. v_id=feature_id,
  1614. c_mapinfo=c_mapinfo,
  1615. c_points=c_points,
  1616. c_cats=c_cats,
  1617. table=table,
  1618. writeable=writeable,
  1619. is2D=is2D,
  1620. free_points=free_points,
  1621. free_cats=free_cats,
  1622. )
  1623. if __name__ == "__main__":
  1624. import doctest
  1625. from grass.pygrass import utils
  1626. utils.create_test_vector_map(test_vector_name)
  1627. doctest.testmod()
  1628. """Remove the generated vector map, if exist"""
  1629. from grass.pygrass.utils import get_mapset_vector
  1630. from grass.script.core import run_command
  1631. mset = get_mapset_vector(test_vector_name, mapset="")
  1632. if mset:
  1633. run_command("g.remove", flags="f", type="vector", name=test_vector_name)