geometry.py 58 KB

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