geometry.py 63 KB

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