geometry.py 58 KB

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