geometry.py 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653
  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. import numpy as np
  9. import grass.lib.gis as libgis
  10. import grass.lib.vector as libvect
  11. from grass.pygrass.errors import GrassError
  12. from grass.pygrass.vector.basic import Ilist, Bbox, Cats
  13. from grass.pygrass.vector import sql
  14. WKT = {'POINT\((.*)\)': 'point', # 'POINT\(\s*([+-]*\d+\.*\d*)+\s*\)'
  15. 'LINESTRING\((.*)\)': 'line'}
  16. def read_WKT(string):
  17. """Read the string and return a geometry object
  18. **WKT**:
  19. ::
  20. POINT(0 0)
  21. LINESTRING(0 0,1 1,1 2)
  22. POLYGON((0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1))
  23. MULTIPOINT(0 0,1 2)
  24. MULTILINESTRING((0 0,1 1,1 2),(2 3,3 2,5 4))
  25. MULTIPOLYGON(((0 0,4 0,4 4,0 4,0 0),(1 1,2 1,2 2,1 2,1 1)),
  26. ((-1 -1,-1 -2,-2 -2,-2 -1,-1 -1)))
  27. GEOMETRYCOLLECTION(POINT(2 3),LINESTRING(2 3,3 4))
  28. **EWKT**:
  29. ::
  30. POINT(0 0 0) -- XYZ
  31. SRID=32632;POINT(0 0) -- XY with SRID
  32. POINTM(0 0 0) -- XYM
  33. POINT(0 0 0 0) -- XYZM
  34. SRID=4326;MULTIPOINTM(0 0 0,1 2 1) -- XYM with SRID
  35. MULTILINESTRING((0 0 0,1 1 0,1 2 1),(2 3 1,3 2 1,5 4 1))
  36. 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))
  37. MULTIPOLYGON(((0 0 0,4 0 0,4 4 0,0 4 0,0 0 0),
  38. (1 1 0,2 1 0,2 2 0,1 2 0,1 1 0)),
  39. ((-1 -1 0,-1 -2 0,-2 -2 0,-2 -1 0,-1 -1 0)))
  40. GEOMETRYCOLLECTIONM( POINTM(2 3 9), LINESTRINGM(2 3 4, 3 4 5) )
  41. MULTICURVE( (0 0, 5 5), CIRCULARSTRING(4 0, 4 4, 8 4) )
  42. POLYHEDRALSURFACE( ((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)),
  43. ((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)),
  44. ((0 0 0, 1 0 0, 1 0 1, 0 0 1, 0 0 0)),
  45. ((1 1 0, 1 1 1, 1 0 1, 1 0 0, 1 1 0)),
  46. ((0 1 0, 0 1 1, 1 1 1, 1 1 0, 0 1 0)),
  47. ((0 0 1, 1 0 1, 1 1 1, 0 1 1, 0 0 1)) )
  48. TRIANGLE ((0 0, 0 9, 9 0, 0 0))
  49. 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)) )
  50. """
  51. for regexp, obj in WKT.items():
  52. if re.match(regexp, string):
  53. geo = 10
  54. return obj(geo)
  55. def read_WKB(buff):
  56. """Read the binary buffer and return a geometry object"""
  57. pass
  58. def intersects(lineA, lineB, with_z=False):
  59. """Return a list of points
  60. >>> lineA = Line([(0, 0), (4, 0)])
  61. >>> lineB = Line([(2, 2), (2, -2)])
  62. >>> intersects(lineA, lineB)
  63. Line([Point(2.000000, 0.000000)])
  64. """
  65. line = Line()
  66. if libvect.Vect_line_get_intersections(lineA.c_points, lineB.c_points,
  67. line.c_points, int(with_z)):
  68. return line
  69. else:
  70. return []
  71. #=============================================
  72. # GEOMETRY
  73. #=============================================
  74. def get_xyz(pnt):
  75. """Return a tuple with: x, y, z.
  76. >>> pnt = Point(0, 0)
  77. >>> get_xyz(pnt)
  78. (0.0, 0.0, 0.0)
  79. >>> get_xyz((1, 1))
  80. (1, 1, 0.0)
  81. >>> get_xyz((1, 1, 2))
  82. (1, 1, 2)
  83. >>> get_xyz((1, 1, 2, 2)) #doctest: +ELLIPSIS
  84. Traceback (most recent call last):
  85. ...
  86. ValueError: The the format of the point is not supported: (1, 1, 2, 2)
  87. """
  88. if isinstance(pnt, Point):
  89. if pnt.is2D:
  90. x, y = pnt.x, pnt.y
  91. z = 0.
  92. else:
  93. x, y, z = pnt.x, pnt.y, pnt.z
  94. else:
  95. if len(pnt) == 2:
  96. x, y = pnt
  97. z = 0.
  98. elif len(pnt) == 3:
  99. x, y, z = pnt
  100. else:
  101. str_error = "The the format of the point is not supported: {0!r}"
  102. raise ValueError(str_error.format(pnt))
  103. return x, y, z
  104. class Attrs(object):
  105. def __init__(self, cat, table, writable=False):
  106. self._cat = None
  107. self.cond = ''
  108. self.table = table
  109. self.cat = cat
  110. self.writable = writable
  111. def _get_cat(self):
  112. return self._cat
  113. def _set_cat(self, value):
  114. self._cat = value
  115. if value:
  116. # update condition
  117. self.cond = "%s=%d" % (self.table.key, value)
  118. cat = property(fget=_get_cat, fset=_set_cat,
  119. doc="Set and obtain cat value")
  120. def __getitem__(self, key):
  121. """Return the value stored in the attribute table.
  122. >>> from grass.pygrass.vector import VectorTopo
  123. >>> schools = VectorTopo('schools')
  124. >>> schools.open('r')
  125. >>> school = schools[1]
  126. >>> attrs = Attrs(school.cat, schools.table)
  127. >>> attrs['TAG']
  128. u'568'
  129. """
  130. #SELECT {cols} FROM {tname} WHERE {condition};
  131. try:
  132. cur = self.table.execute(sql.SELECT_WHERE.format(cols=key,
  133. tname=self.table.name,
  134. condition=self.cond))
  135. except:
  136. import ipdb; ipdb.set_trace()
  137. results = cur.fetchone()
  138. if results is not None:
  139. return results[0] if len(results) == 1 else results
  140. def __setitem__(self, key, value):
  141. """Set value of a given column of a table attribute.
  142. >>> from grass.pygrass.vector import VectorTopo
  143. >>> from grass.pygrass.functions import copy, remove
  144. >>> copy('schools', 'myschools', 'vect')
  145. >>> schools = VectorTopo('myschools')
  146. >>> schools.open('r')
  147. >>> school = schools[1]
  148. >>> attrs = Attrs(school.cat, schools.table, True)
  149. >>> attrs['TAG'] = 'New Label'
  150. >>> attrs['TAG']
  151. u'New Label'
  152. >>> attrs.table.conn.close()
  153. >>> remove('myschools','vect')
  154. """
  155. if self.writable:
  156. #UPDATE {tname} SET {new_col} = {old_col} WHERE {condition}
  157. values = '%s=%r' % (key, value)
  158. self.table.execute(sql.UPDATE_WHERE.format(tname=self.table.name,
  159. values=values,
  160. condition=self.cond))
  161. #self.table.conn.commit()
  162. else:
  163. str_err = "You can only read the attributes if the map is in another mapset"
  164. raise GrassError(str_err)
  165. def __dict__(self):
  166. """Return a dict of the attribute table row."""
  167. dic = {}
  168. for key, val in zip(self.keys(), self.values()):
  169. dic[key] = val
  170. return dic
  171. def values(self):
  172. """Return the values of the attribute table row.
  173. >>> from grass.pygrass.vector import VectorTopo
  174. >>> schools = VectorTopo('schools')
  175. >>> schools.open('r')
  176. >>> school = schools[1]
  177. >>> attrs = Attrs(school.cat, schools.table)
  178. >>> attrs.values() # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
  179. (1,
  180. ...
  181. None)
  182. """
  183. #SELECT {cols} FROM {tname} WHERE {condition}
  184. cur = self.table.execute(sql.SELECT_WHERE.format(cols='*',
  185. tname=self.table.name,
  186. condition=self.cond))
  187. return cur.fetchone()
  188. def keys(self):
  189. """Return the column name of the attribute table.
  190. >>> from grass.pygrass.vector import VectorTopo
  191. >>> schools = VectorTopo('schools')
  192. >>> schools.open('r')
  193. >>> school = schools[1]
  194. >>> attrs = Attrs(school.cat, schools.table)
  195. >>> attrs.keys() # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
  196. [u'cat',
  197. ...
  198. u'NOTES']
  199. """
  200. return self.table.columns.names()
  201. def commit(self):
  202. """Save the changes"""
  203. self.table.conn.commit()
  204. class Geo(object):
  205. """
  206. >>> geo0 = Geo()
  207. >>> points = ctypes.pointer(libvect.line_pnts())
  208. >>> cats = ctypes.pointer(libvect.line_cats())
  209. >>> geo1 = Geo(c_points=points, c_cats=cats)
  210. """
  211. gtype = None
  212. def __init__(self, v_id=0, c_mapinfo=None, c_points=None, c_cats=None,
  213. table=None, writable=False, is2D=True):
  214. self.id = v_id # vector id
  215. self.c_mapinfo = c_mapinfo
  216. self.is2D = (is2D if is2D is not None else
  217. bool(libvect.Vect_is_3d(self.c_mapinfo) != 1))
  218. read = False
  219. # set c_points
  220. if c_points is None:
  221. self.c_points = ctypes.pointer(libvect.line_pnts())
  222. read = True
  223. else:
  224. self.c_points = c_points
  225. # set c_cats
  226. if c_cats is None:
  227. self.c_cats = ctypes.pointer(libvect.line_cats())
  228. read = True
  229. else:
  230. self.c_cats = c_cats
  231. # set the attributes
  232. self.attrs = None
  233. if table is not None:
  234. self.attrs = Attrs(self.cat, table, writable)
  235. if self.id and self.c_mapinfo is not None and read:
  236. self.read()
  237. @property
  238. def cat(self):
  239. if self.c_cats.contents.cat:
  240. return self.c_cats.contents.cat.contents.value
  241. def is_with_topology(self):
  242. if self.c_mapinfo is not None:
  243. return self.c_mapinfo.contents.level == 2
  244. else:
  245. return False
  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.get_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 get_wkt(self):
  349. """Return a "well know text" (WKT) geometry string. ::
  350. >>> pnt = Point(10, 100)
  351. >>> pnt.get_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 get_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.get_wkt()
  495. def __repr__(self):
  496. return "Line([%s])" % ', '.join([repr(pnt) for pnt in self.__iter__()])
  497. def get_pnt(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.get_pnt(5) #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
  503. Traceback (most recent call last):
  504. ...
  505. ValueError: The distance exceed the lenght of the line,
  506. that is: 1.414214
  507. >>> line.get_pnt(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 lenght of the line, that is: %f"
  515. raise ValueError(str_err % maxdist)
  516. pnt = Point(0, 0, -9999)
  517. 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. pnt.is2D = self.is2D
  523. return pnt
  524. def append(self, pnt):
  525. """Appends one point to the end of a line, using the
  526. ``Vect_append_point`` C function.
  527. :param pnt: the point to add to line
  528. :type pnt: a Point object or a tuple with the coordinates
  529. >>> line = Line()
  530. >>> line.append((10, 100))
  531. >>> line
  532. Line([Point(10.000000, 100.000000)])
  533. >>> line.append((20, 200))
  534. >>> line
  535. Line([Point(10.000000, 100.000000), Point(20.000000, 200.000000)])
  536. Like python list.
  537. """
  538. x, y, z = get_xyz(pnt)
  539. libvect.Vect_append_point(self.c_points, x, y, z)
  540. def bbox(self, bbox=None):
  541. """Return the bounding box of the line, using ``Vect_line_box``
  542. C function. ::
  543. >>> line = Line([(0, 0), (0, 1), (2, 1), (2, 0)])
  544. >>> bbox = line.bbox()
  545. >>> bbox
  546. Bbox(1.0, 0.0, 2.0, 0.0)
  547. ..
  548. """
  549. bbox = bbox if bbox else Bbox()
  550. libvect.Vect_line_box(self.c_points, bbox.c_bbox)
  551. return bbox
  552. def extend(self, line, forward=True):
  553. """Appends points to the end of a line.
  554. :param line: it is possible to extend a line, give a list of points,
  555. or directly with a line_pnts struct.
  556. :type line: Line object ot list of points
  557. :param forward: if forward is True the line is extend forward otherwise
  558. is extend backward. The method use the
  559. `Vect_append_points` C function.
  560. :type forward: bool
  561. >>> line = Line([(0, 0), (1, 1)])
  562. >>> line.extend( Line([(2, 2), (3, 3)]) )
  563. >>> line #doctest: +NORMALIZE_WHITESPACE
  564. Line([Point(0.000000, 0.000000),
  565. Point(1.000000, 1.000000),
  566. Point(2.000000, 2.000000),
  567. Point(3.000000, 3.000000)])
  568. """
  569. # set direction
  570. if forward:
  571. direction = libvect.GV_FORWARD
  572. else:
  573. direction = libvect.GV_BACKWARD
  574. # check if is a Line object
  575. if isinstance(line, Line):
  576. c_points = line.c_points
  577. else:
  578. # instantiate a Line object
  579. lin = Line()
  580. for pnt in line:
  581. # add the points to the line
  582. lin.append(pnt)
  583. c_points = lin.c_points
  584. libvect.Vect_append_points(self.c_points, c_points, direction)
  585. def insert(self, indx, pnt):
  586. """Insert new point at index position and move all old points at
  587. that position and above up, using ``Vect_line_insert_point``
  588. C function.
  589. :param indx: the index where add new point
  590. :type indx: int
  591. :param pnt: the point to add
  592. :type pnt: a Point object
  593. >>> line = Line([(0, 0), (1, 1)])
  594. >>> line.insert(0, Point(1.000000, -1.000000) )
  595. >>> line #doctest: +NORMALIZE_WHITESPACE
  596. Line([Point(1.000000, -1.000000),
  597. Point(0.000000, 0.000000),
  598. Point(1.000000, 1.000000)])
  599. """
  600. if indx < 0: # Handle negative indices
  601. indx += self.c_points.contents.n_points
  602. if indx >= self.c_points.contents.n_points:
  603. raise IndexError('Index out of range')
  604. x, y, z = get_xyz(pnt)
  605. libvect.Vect_line_insert_point(self.c_points, indx, x, y, z)
  606. def length(self):
  607. """Calculate line length, 3D-length in case of 3D vector line, using
  608. `Vect_line_length` C function. ::
  609. >>> line = Line([(0, 0), (1, 1), (0, 1)])
  610. >>> line.length()
  611. 2.414213562373095
  612. ..
  613. """
  614. return libvect.Vect_line_length(self.c_points)
  615. def length_geodesic(self):
  616. """Calculate line length, usig `Vect_line_geodesic_length` C function.
  617. ::
  618. >>> line = Line([(0, 0), (1, 1), (0, 1)])
  619. >>> line.length_geodesic()
  620. 2.414213562373095
  621. ..
  622. """
  623. return libvect.Vect_line_geodesic_length(self.c_points)
  624. def distance(self, pnt):
  625. """Calculate the distance between line and a point.
  626. :param pnt: the point to calculate distance
  627. :type pnt: a Point object or a tuple with the coordinates
  628. Return a tuple with:
  629. * the closest point on the line,
  630. * the distance between these two points,
  631. * distance of point from segment beginning
  632. * distance of point from line
  633. The distance is compute using the ``Vect_line_distance`` C function.
  634. >>> line = Line([(0, 0), (0, 2)])
  635. >>> line.distance(Point(1, 1))
  636. (Point(0.000000, 1.000000), 1.0, 1.0, 1.0)
  637. """
  638. # instantite outputs
  639. cx = ctypes.c_double(0)
  640. cy = ctypes.c_double(0)
  641. cz = ctypes.c_double(0)
  642. dist = ctypes.c_double(0)
  643. sp_dist = ctypes.c_double(0)
  644. lp_dist = ctypes.c_double(0)
  645. libvect.Vect_line_distance(self.c_points,
  646. pnt.x, pnt.y, 0 if pnt.is2D else pnt.z,
  647. 0 if self.is2D else 1,
  648. ctypes.byref(cx), ctypes.byref(cy),
  649. ctypes.byref(cz), ctypes.byref(dist),
  650. ctypes.byref(sp_dist),
  651. ctypes.byref(lp_dist))
  652. # instantiate the Point class
  653. point = Point(cx.value, cy.value, cz.value)
  654. point.is2D = self.is2D
  655. return point, dist.value, sp_dist.value, lp_dist.value
  656. def get_first_cat(self):
  657. """Fetches FIRST category number for given vector line and field, using
  658. the ``Vect_get_line_cat`` C function.
  659. .. warning::
  660. Not implemented yet.
  661. """
  662. # TODO: add this method.
  663. libvect.Vect_get_line_cat(self.map, self.id, self.field)
  664. pass
  665. def pop(self, indx):
  666. """Return the point in the index position and remove from the Line.
  667. :param indx: the index where add new point
  668. :type indx: int
  669. >>> line = Line([(0, 0), (1, 1), (2, 2)])
  670. >>> midle_pnt = line.pop(1)
  671. >>> midle_pnt
  672. Point(1.000000, 1.000000)
  673. >>> line
  674. Line([Point(0.000000, 0.000000), Point(2.000000, 2.000000)])
  675. """
  676. if indx < 0: # Handle negative indices
  677. indx += self.c_points.contents.n_points
  678. if indx >= self.c_points.contents.n_points:
  679. raise IndexError('Index out of range')
  680. pnt = self.__getitem__(indx)
  681. libvect.Vect_line_delete_point(self.c_points, indx)
  682. return pnt
  683. def delete(self, indx):
  684. """Remove the point in the index position.
  685. :param indx: the index where add new point
  686. :type indx: int
  687. >>> line = Line([(0, 0), (1, 1), (2, 2)])
  688. >>> line.delete(-1)
  689. >>> line
  690. Line([Point(0.000000, 0.000000), Point(1.000000, 1.000000)])
  691. """
  692. if indx < 0: # Handle negative indices
  693. indx += self.c_points.contents.n_points
  694. if indx >= self.c_points.contents.n_points:
  695. raise IndexError('Index out of range')
  696. libvect.Vect_line_delete_point(self.c_points, indx)
  697. def prune(self):
  698. """Remove duplicate points, i.e. zero length segments, using
  699. `Vect_line_prune` C function. ::
  700. >>> line = Line([(0, 0), (1, 1), (1, 1), (2, 2)])
  701. >>> line.prune()
  702. >>> line #doctest: +NORMALIZE_WHITESPACE
  703. Line([Point(0.000000, 0.000000),
  704. Point(1.000000, 1.000000),
  705. Point(2.000000, 2.000000)])
  706. ..
  707. """
  708. libvect.Vect_line_prune(self.c_points)
  709. def prune_thresh(self, threshold):
  710. """Remove points in threshold, using the ``Vect_line_prune_thresh``
  711. C funtion.
  712. :param threshold: the threshold value where prune points
  713. :type threshold: num
  714. >>> line = Line([(0, 0), (1.0, 1.0), (1.2, 0.9), (2, 2)])
  715. >>> line.prune_thresh(0.5)
  716. >>> line #doctest: +SKIP +NORMALIZE_WHITESPACE
  717. Line([Point(0.000000, 0.000000),
  718. Point(1.000000, 1.000000),
  719. Point(2.000000, 2.000000)])
  720. .. warning ::
  721. prune_thresh is not working yet.
  722. """
  723. libvect.Vect_line_prune(self.c_points, ctypes.c_double(threshold))
  724. def remove(self, pnt):
  725. """Delete point at given index and move all points above down, using
  726. `Vect_line_delete_point` C function.
  727. :param pnt: the point to remove
  728. :type pnt: a Point object or a tuple with the coordinates
  729. >>> line = Line([(0, 0), (1, 1), (2, 2)])
  730. >>> line.remove((2, 2))
  731. >>> line[-1]
  732. Point(1.000000, 1.000000)
  733. ..
  734. """
  735. for indx, point in enumerate(self.__iter__()):
  736. if pnt == point:
  737. libvect.Vect_line_delete_point(self.c_points, indx)
  738. return
  739. raise ValueError('list.remove(x): x not in list')
  740. def reverse(self):
  741. """Reverse the order of vertices, using `Vect_line_reverse`
  742. C function. ::
  743. >>> line = Line([(0, 0), (1, 1), (2, 2)])
  744. >>> line.reverse()
  745. >>> line #doctest: +NORMALIZE_WHITESPACE
  746. Line([Point(2.000000, 2.000000),
  747. Point(1.000000, 1.000000),
  748. Point(0.000000, 0.000000)])
  749. ..
  750. """
  751. libvect.Vect_line_reverse(self.c_points)
  752. def segment(self, start, end):
  753. # TODO improve documentation
  754. """Create line segment. using the ``Vect_line_segment`` C function.
  755. """
  756. line = Line()
  757. libvect.Vect_line_segment(self.c_points, start, end, line.c_points)
  758. return line
  759. def tolist(self):
  760. """Return a list of tuple. ::
  761. >>> line = Line([(0, 0), (1, 1), (2, 0), (1, -1)])
  762. >>> line.tolist()
  763. [(0.0, 0.0), (1.0, 1.0), (2.0, 0.0), (1.0, -1.0)]
  764. ..
  765. """
  766. return [pnt.coords() for pnt in self.__iter__()]
  767. def toarray(self):
  768. """Return an array of coordinates. ::
  769. >>> line = Line([(0, 0), (1, 1), (2, 0), (1, -1)])
  770. >>> line.toarray() #doctest: +NORMALIZE_WHITESPACE
  771. array([[ 0., 0.],
  772. [ 1., 1.],
  773. [ 2., 0.],
  774. [ 1., -1.]])
  775. ..
  776. """
  777. return np.array(self.tolist())
  778. def get_wkt(self):
  779. """Return a Well Known Text string of the line. ::
  780. >>> line = Line([(0, 0), (1, 1), (1, 2)])
  781. >>> line.get_wkt() #doctest: +ELLIPSIS
  782. 'LINESTRING(0.000000 0.000000, ..., 1.000000 2.000000)'
  783. ..
  784. """
  785. return "LINESTRING(%s)" % ', '.join([
  786. ' '.join(['%f' % coord for coord in pnt.coords()])
  787. for pnt in self.__iter__()])
  788. def from_wkt(self, wkt):
  789. """Create a line reading a WKT string.
  790. :param wkt: the WKT string containing the LINESTRING
  791. :type wkt: str
  792. >>> line = Line()
  793. >>> line.from_wkt("LINESTRING(0 0,1 1,1 2)")
  794. >>> line #doctest: +NORMALIZE_WHITESPACE
  795. Line([Point(0.000000, 0.000000),
  796. Point(1.000000, 1.000000),
  797. Point(1.000000, 2.000000)])
  798. ..
  799. """
  800. match = re.match('LINESTRING\((.*)\)', wkt)
  801. if match:
  802. self.reset()
  803. for coord in match.groups()[0].strip().split(','):
  804. self.append(tuple([float(e) for e in coord.split(' ')]))
  805. else:
  806. return None
  807. def get_wkb(self):
  808. """Return a WKB buffer.
  809. .. warning::
  810. Not implemented yet.
  811. """
  812. pass
  813. def buffer(self, dist=None, dist_x=None, dist_y=None,
  814. angle=0, round_=True, caps=True, tol=0.1):
  815. """Return the buffer area around the line, using the
  816. ``Vect_line_buffer2`` C function.
  817. :param dist: the distance around the line
  818. :type dist: num
  819. :param dist_x: the distance along x
  820. :type dist_x: num
  821. :param dist_y: the distance along y
  822. :type dist_y: num
  823. :param angle: the angle between 0x and major axis
  824. :type angle: num
  825. :param round_: to make corners round
  826. :type round_: bool
  827. :param tol: fix the maximum distance between theoretical arc and
  828. output segments
  829. :type tol: float
  830. :returns: the buffer as Area object
  831. >>> line = Line([(0, 0), (0, 2)])
  832. >>> area = line.buffer(10)
  833. >>> area.boundary #doctest: +ELLIPSIS
  834. Line([Point(-10.000000, 0.000000),...Point(-10.000000, 0.000000)])
  835. >>> area.centroid
  836. Point(0.000000, 0.000000)
  837. >>> area.isles
  838. []
  839. ..
  840. """
  841. if dist is not None:
  842. dist_x = dist
  843. dist_y = dist
  844. elif not dist_x or not dist_y:
  845. raise TypeError('TypeError: buffer expected 1 arguments, got 0')
  846. p_bound = ctypes.pointer(ctypes.pointer(libvect.line_pnts()))
  847. pp_isle = ctypes.pointer(ctypes.pointer(
  848. ctypes.pointer(libvect.line_pnts())))
  849. n_isles = ctypes.pointer(ctypes.c_int())
  850. libvect.Vect_line_buffer2(self.c_points,
  851. dist_x, dist_y, angle,
  852. int(round_), int(caps), tol,
  853. p_bound, pp_isle, n_isles)
  854. return Area(boundary=Line(c_points=p_bound.contents),
  855. centroid=self[0],
  856. isles=[Line(c_points=pp_isle[i].contents)
  857. for i in range(n_isles.contents.value)])
  858. def reset(self):
  859. """Reset line, using `Vect_reset_line` C function. ::
  860. >>> line = Line([(0, 0), (1, 1), (2, 0), (1, -1)])
  861. >>> len(line)
  862. 4
  863. >>> line.reset()
  864. >>> len(line)
  865. 0
  866. >>> line
  867. Line([])
  868. ..
  869. """
  870. libvect.Vect_reset_line(self.c_points)
  871. def nodes(self):
  872. """Return the nodes in the line"""
  873. if self.is_with_topology():
  874. n1 = ctypes.c_int()
  875. n2 = ctypes.c_int()
  876. libvect.Vect_get_line_nodes(self.c_mapinfo, self.id,
  877. ctypes.byref(n1),
  878. ctypes.byref(n2))
  879. return (Node(n1.value, self.c_mapinfo),
  880. Node(n2.value, self.c_mapinfo))
  881. class Node(object):
  882. def __init__(self, v_id, c_mapinfo):
  883. self.id = v_id # vector id
  884. self.c_mapinfo = c_mapinfo
  885. self.is2D = bool(libvect.Vect_is_3d(self.c_mapinfo) != 1)
  886. self.nlines = libvect.Vect_get_node_n_lines(self.c_mapinfo, self.id)
  887. def __len__(self):
  888. return self.nlines
  889. def __iter__(self):
  890. return self.ilines()
  891. def __repr__(self):
  892. return "Node(%d)" % self.id
  893. def coords(self):
  894. """Return a tuple with the node coordinates."""
  895. x = ctypes.c_double()
  896. y = ctypes.c_double()
  897. z = ctypes.c_double()
  898. libvect.Vect_get_node_coor(self.c_mapinfo, self.id, ctypes.byref(x),
  899. ctypes.byref(y), ctypes.byref(z))
  900. return (x.value, y.value) if self.is2D else (x.value, y.value, z.value)
  901. def ilines(self, only_in=False, only_out=False):
  902. """Return a generator with all lines id connected to a node.
  903. The line id is negative if line is ending on the node and positive if
  904. starting from the node.
  905. :param only_in: Return only the lines that are ending in the node
  906. :type only_in: bool
  907. :param only_out: Return only the lines that are starting in the node
  908. :type only_out: bool
  909. """
  910. for iline in range(self.nlines):
  911. lid = libvect.Vect_get_node_line(self.c_mapinfo, self.id, iline)
  912. if (not only_in and lid > 0) or (not only_out and lid < 0):
  913. yield lid
  914. def lines(self, only_in=False, only_out=False):
  915. """Return a generator with all lines connected to a node.
  916. :param only_in: Return only the lines that are ending in the node
  917. :type only_in: bool
  918. :param only_out: Return only the lines that are starting in the node
  919. :type only_out: bool
  920. """
  921. for iline in self.ilines(only_in, only_out):
  922. yield Line(id=abs(iline), c_mapinfo=self.c_mapinfo)
  923. def angles(self):
  924. """Return a generator with all lines angles in a node."""
  925. for iline in range(self.nlines):
  926. yield libvect.Vect_get_node_line_angle(self.c_mapinfo,
  927. self.id, iline)
  928. class Boundary(Line):
  929. """
  930. """
  931. # geometry type
  932. gtype = libvect.GV_BOUNDARY
  933. def __init__(self, lines=None, left=None, right=None,
  934. **kargs):
  935. v_id = kargs.get('v_id', 0)
  936. self.dir = libvect.GV_FORWARD if v_id > 0 else libvect.GV_BACKWARD
  937. super(Boundary, self).__init__(**kargs)
  938. self.c_left = ctypes.pointer(ctypes.c_int())
  939. self.c_right = ctypes.pointer(ctypes.c_int())
  940. #self.get_left_right()
  941. @property
  942. def left_id(self):
  943. return self.c_left.contents.value
  944. @property
  945. def right_id(self):
  946. return self.c_right.contents.value
  947. def __repr__(self):
  948. return "Boundary(v_id=%r)" % self.id
  949. def _get_centroid(self, side, idonly=False):
  950. if side > 0:
  951. v_id = libvect.Vect_get_area_centroid(self.c_mapinfo, side)
  952. v_id = v_id if v_id else None
  953. if idonly:
  954. return v_id
  955. else:
  956. cntr = Centroid(v_id=v_id, c_mapinfo=self.c_mapinfo)
  957. return cntr
  958. def get_left_centroid(self, idonly=False):
  959. """Return left value
  960. :param idonly: True to return only the cat of feature
  961. :type idonly: bool
  962. """
  963. return self._get_centroid(self.left_id, idonly)
  964. def get_right_centroid(self, idonly=False):
  965. """Return right value
  966. :param idonly: True to return only the cat of feature
  967. :type idonly: bool
  968. """
  969. return self._get_centroid(self.left_id, idonly)
  970. def get_left_right(self):
  971. """Return left and right value"""
  972. libvect.Vect_get_line_areas(self.c_mapinfo, self.id,
  973. self.c_left, self.c_right)
  974. return self.c_left.contents.value, self.c_right.contents.value
  975. def area(self):
  976. """Return the area of the polygon.
  977. >>> bound = Boundary(points=[(0, 0), (0, 2), (2, 2), (2, 0),
  978. ... (0, 0)])
  979. >>> bound.area()
  980. 4.0
  981. """
  982. libgis.G_begin_polygon_area_calculations()
  983. return libgis.G_area_of_polygon(self.c_points.contents.x,
  984. self.c_points.contents.y,
  985. self.c_points.contents.n_points)
  986. class Centroid(Point):
  987. """The Centroid class inherit from the Point class.
  988. Centroid contains an attribute with the C Map_info struct, and attributes
  989. with the id of the Area. ::
  990. >>> centroid = Centroid(x=0, y=10)
  991. >>> centroid
  992. Centoid(0.000000, 10.000000)
  993. >>> from grass.pygrass.vector import VectorTopo
  994. >>> geo = VectorTopo('geology')
  995. >>> geo.open(mode='r')
  996. >>> centroid = Centroid(v_id=1, c_mapinfo=geo.c_mapinfo)
  997. >>> centroid
  998. Centoid(893202.874416, 297339.312795)
  999. ..
  1000. """
  1001. # geometry type
  1002. gtype = libvect.GV_CENTROID
  1003. def __init__(self, area_id=None, **kargs):
  1004. super(Centroid, self).__init__(**kargs)
  1005. self.area_id = area_id
  1006. if self.id and self.c_mapinfo and self.area_id is None:
  1007. self.area_id = self.get_area_id()
  1008. elif self.c_mapinfo and self.area_id and self.id is None:
  1009. self.id = self.get_centroid_id()
  1010. if self.area_id is not None:
  1011. self.read()
  1012. #self.c_pline = ctypes.pointer(libvect.P_line()) if topology else None
  1013. def __repr__(self):
  1014. return "Centoid(%s)" % ', '.join(['%f' % co for co in self.coords()])
  1015. def get_centroid_id(self):
  1016. """Return the centroid_id, using the c_mapinfo and an area_id
  1017. attributes of the class, and calling the Vect_get_area_centroid
  1018. C function, if no centroid_id were found return None"""
  1019. centroid_id = libvect.Vect_get_area_centroid(self.c_mapinfo,
  1020. self.area_id)
  1021. return centroid_id if centroid_id != 0 else None
  1022. def get_area_id(self):
  1023. """Return the area_id, using the c_mapinfo and an centroid_id
  1024. attributes of the class, and calling the Vect_get_centroid_area
  1025. C function, if no area_id were found return None"""
  1026. area_id = libvect.Vect_get_centroid_area(self.c_mapinfo,
  1027. self.id)
  1028. return area_id if area_id != 0 else None
  1029. class Isle(Geo):
  1030. """An Isle is an area contained by another area.
  1031. """
  1032. def __init__(self, **kargs):
  1033. super(Isle, self).__init__(**kargs)
  1034. #self.area_id = area_id
  1035. def __repr__(self):
  1036. return "Isle(%d)" % (self.id)
  1037. def boundaries(self):
  1038. """Return a list of boundaries"""
  1039. ilist = Ilist()
  1040. libvect.Vect_get_isle_boundaries(self.c_mapinfo, self.id,
  1041. ilist.c_ilist)
  1042. return ilist
  1043. def bbox(self, bbox=None):
  1044. """Return bounding box of Isle"""
  1045. bbox = bbox if bbox else Bbox()
  1046. libvect.Vect_get_isle_box(self.c_mapinfo, self.id, bbox.c_bbox)
  1047. return bbox
  1048. def points(self):
  1049. """Return a Line object with the outer ring points"""
  1050. line = Line()
  1051. libvect.Vect_get_isle_points(self.c_mapinfo, self.id, line.c_points)
  1052. return line
  1053. def points_geos(self):
  1054. """Return a Line object with the outer ring points
  1055. """
  1056. return libvect.Vect_get_isle_points_geos(self.c_mapinfo, self.id)
  1057. def area_id(self):
  1058. """Returns area id for isle."""
  1059. return libvect.Vect_get_isle_area(self.c_mapinfo, self.id)
  1060. def alive(self):
  1061. """Check if isle is alive or dead (topology required)"""
  1062. return bool(libvect.Vect_isle_alive(self.c_mapinfo, self.id))
  1063. def contain_pnt(self, pnt):
  1064. """Check if point is in area.
  1065. :param pnt: the point to remove
  1066. :type pnt: a Point object or a tuple with the coordinates
  1067. """
  1068. bbox = self.bbox()
  1069. return bool(libvect.Vect_point_in_island(pnt.x, pnt.y,
  1070. self.c_mapinfo, self.id,
  1071. bbox.c_bbox.contents))
  1072. def area(self):
  1073. """Return the area value of an Isle"""
  1074. border = self.points()
  1075. return libgis.G_area_of_polygon(border.c_points.contents.x,
  1076. border.c_points.contents.y,
  1077. border.c_points.contents.n_points)
  1078. def perimeter(self):
  1079. """Return the perimeter value of an Isle.
  1080. """
  1081. border = self.points()
  1082. return libvect.Vect_area_perimeter(border.c_points)
  1083. class Isles(object):
  1084. def __init__(self, c_mapinfo, area_id=None):
  1085. self.c_mapinfo = c_mapinfo
  1086. self.area_id = area_id
  1087. self._isles_id = None
  1088. self._isles = None
  1089. if area_id:
  1090. self._isles_id = self.get_isles_id()
  1091. self._isles = self.get_isles()
  1092. def __len__(self):
  1093. return libvect.Vect_get_area_num_isles(self.c_mapinfo, self.area_id)
  1094. def __repr__(self):
  1095. return "Isles(%r)" % self.area_id
  1096. def __getitem__(self, key):
  1097. if self._isles is None:
  1098. self.get_isles()
  1099. return self._isles[key]
  1100. def get_isles_id(self):
  1101. """Return the id of isles"""
  1102. return [libvect.Vect_get_area_isle(self.c_mapinfo, self.area_id, i)
  1103. for i in range(self.__len__())]
  1104. def get_isles(self):
  1105. """Return isles"""
  1106. return [Isle(v_id=isle_id, c_mapinfo=self.c_mapinfo)
  1107. for isle_id in self._isles_id]
  1108. def select_by_bbox(self, bbox):
  1109. """Vect_select_isles_by_box
  1110. .. warning::
  1111. Not implemented yet.
  1112. """
  1113. pass
  1114. class Area(Geo):
  1115. """
  1116. Vect_build_line_area,
  1117. Vect_find_area,
  1118. Vect_get_area_box,
  1119. Vect_get_area_points_geos,
  1120. Vect_get_centroid_area,
  1121. Vect_get_isle_area,
  1122. Vect_get_line_areas,
  1123. Vect_get_num_areas,
  1124. Vect_get_point_in_area,
  1125. Vect_isle_find_area,
  1126. Vect_point_in_area,
  1127. Vect_point_in_area_outer_ring,
  1128. Vect_read_area_geos,
  1129. Vect_remove_small_areas,
  1130. Vect_select_areas_by_box,
  1131. Vect_select_areas_by_polygon
  1132. """
  1133. # geometry type
  1134. gtype = libvect.GV_AREA
  1135. def __init__(self, boundary=None, centroid=None, isles=None, **kargs):
  1136. super(Area, self).__init__(**kargs)
  1137. self.boundary = None
  1138. self.centroid = None
  1139. self.isles = None
  1140. if boundary and centroid:
  1141. self.boundary = boundary
  1142. self.centroid = centroid
  1143. self.isles = isles if isles else []
  1144. # set the attributes
  1145. if self.attrs and self.cat:
  1146. self.attrs.cat = self.cat
  1147. def __repr__(self):
  1148. return "Area(%d)" % self.id if self.id else "Area( )"
  1149. def init_from_id(self, area_id=None):
  1150. """Return an Area object"""
  1151. if area_id is None and self.id is None:
  1152. raise ValueError("You need to give or set the area_id")
  1153. self.id = area_id if area_id is not None else self.id
  1154. # get boundary
  1155. self.get_points()
  1156. # get isles
  1157. self.get_isles()
  1158. def get_points(self, line=None):
  1159. """Return a Line object with the outer ring
  1160. :param line: a Line object to fill with info from points of area
  1161. :type line: a Line object
  1162. """
  1163. line = Line() if line is None else line
  1164. libvect.Vect_get_area_points(self.c_mapinfo, self.id, line.c_points)
  1165. return line
  1166. def get_centroid(self, centroid=None):
  1167. """Return the centroid
  1168. :param centroid: a Centroid object to fill with info from centroid of area
  1169. :type centroid: a Centroid object
  1170. """
  1171. centroid_id = libvect.Vect_get_area_centroid(self.c_mapinfo, self.id)
  1172. if centroid_id:
  1173. if centroid:
  1174. centroid.id = centroid_id
  1175. centroid.read()
  1176. return centroid
  1177. return Centroid(v_id=centroid_id, c_mapinfo=self.c_mapinfo,
  1178. area_id=self.id)
  1179. def num_isles(self):
  1180. return libvect.Vect_get_area_num_isles(self.c_mapinfo, self.id)
  1181. def get_isles(self, isles=None):
  1182. """Instantiate the boundary attribute reading area_id"""
  1183. if isles is not None:
  1184. isles.area_id = self.id
  1185. return isles
  1186. return Isles(self.c_mapinfo, self.id)
  1187. def area(self):
  1188. """Returns area of area without areas of isles.
  1189. double Vect_get_area_area (const struct Map_info \*Map, int area)
  1190. """
  1191. return libvect.Vect_get_area_area(self.c_mapinfo, self.id)
  1192. def alive(self):
  1193. """Check if area is alive or dead (topology required)
  1194. """
  1195. return bool(libvect.Vect_area_alive(self.c_mapinfo, self.id))
  1196. def bbox(self, bbox=None):
  1197. """Return the Bbox of area
  1198. :param bbox: a Bbox object to fill with info from bounding box of area
  1199. :type bbox: a Bbox object
  1200. """
  1201. bbox = bbox if bbox else Bbox()
  1202. libvect.Vect_get_area_box(self.c_mapinfo, self.id, bbox.c_bbox)
  1203. return bbox
  1204. def buffer(self, dist=None, dist_x=None, dist_y=None,
  1205. angle=0, round_=True, caps=True, tol=0.1):
  1206. """Return the buffer area around the area, using the
  1207. ``Vect_area_buffer2`` C function.
  1208. :param dist: the distance around the area
  1209. :type dist: num
  1210. :param dist_x: the distance along x
  1211. :type dist_x: num
  1212. :param dist_y: the distance along y
  1213. :type dist_y: num
  1214. :param angle: the angle between 0x and major axis
  1215. :type angle: num
  1216. :param round_: to make corners round
  1217. :type round_: bool
  1218. :param tol: fix the maximum distance between theoretical arc and
  1219. output segments
  1220. :type tol: float
  1221. :returns: the buffer as Area object
  1222. """
  1223. if dist is not None:
  1224. dist_x = dist
  1225. dist_y = dist
  1226. elif not dist_x or not dist_y:
  1227. raise TypeError('TypeError: buffer expected 1 arguments, got 0')
  1228. p_bound = ctypes.pointer(ctypes.pointer(libvect.line_pnts()))
  1229. pp_isle = ctypes.pointer(ctypes.pointer(
  1230. ctypes.pointer(libvect.line_pnts())))
  1231. n_isles = ctypes.pointer(ctypes.c_int())
  1232. libvect.Vect_area_buffer2(self.c_mapinfo, self.id,
  1233. dist_x, dist_y, angle,
  1234. int(round_), int(caps), tol,
  1235. p_bound, pp_isle, n_isles)
  1236. return Area(boundary=Line(c_points=p_bound.contents),
  1237. centroid=self.centroid,
  1238. isles=[Line(c_points=pp_isle[i].contents)
  1239. for i in range(n_isles.contents.value)])
  1240. def boundaries(self, ilist=False):
  1241. """Creates list of boundaries for given area.
  1242. int Vect_get_area_boundaries(const struct Map_info \*Map,
  1243. int area, struct ilist \*List)
  1244. """
  1245. ilst = Ilist()
  1246. libvect.Vect_get_area_boundaries(self.c_mapinfo, self.id,
  1247. ilst.c_ilist)
  1248. if ilist:
  1249. return ilist
  1250. return [Boundary(v_id, c_mapinfo=self.c_mapinfo) for v_id in ilst]
  1251. def cats(self, cats=None):
  1252. """Get area categories.
  1253. :param cats: a Cats object to fill with info with area categories
  1254. :type cats: a Cats object
  1255. """
  1256. cats = cats if cats else Cats()
  1257. libvect.Vect_get_area_cats(self.c_mapinfo, self.id, cats.c_cats)
  1258. return cats
  1259. def get_first_cat(self):
  1260. """Find FIRST category of given field and area.
  1261. int Vect_get_area_cat(const struct Map_info \*Map, int area, int field)
  1262. """
  1263. pass
  1264. def contain_pnt(self, pnt, bbox=None):
  1265. """Check if point is in area.
  1266. :param pnt: the point to analyze
  1267. :type pnt: a Point object or a tuple with the coordinates
  1268. :param bbox: the bounding box where run the analysis
  1269. :type bbox: a Bbox object
  1270. """
  1271. bbox = bbox if bbox else self.bbox()
  1272. return bool(libvect.Vect_point_in_area(pnt.x, pnt.y,
  1273. self.c_mapinfo, self.id,
  1274. bbox.c_bbox))
  1275. def perimeter(self):
  1276. """Calculate area perimeter.
  1277. :return: double Vect_area_perimeter (const struct line_pnts \*Points)
  1278. """
  1279. border = self.get_points()
  1280. return libvect.Vect_area_perimeter(border.c_points)
  1281. def read(self, line=None, centroid=None, isles=None):
  1282. self.boundary = self.get_points(line)
  1283. self.centroid = self.get_centroid(centroid)
  1284. #self.isles = self.get_isles(isles)
  1285. if self.centroid:
  1286. libvect.Vect_read_line(self.c_mapinfo, None, self.c_cats,
  1287. self.centroid.id)
  1288. #
  1289. # Define a dictionary to convert the feature type to name and or object
  1290. #
  1291. GV_TYPE = {libvect.GV_POINT: {'label': 'point', 'obj': Point},
  1292. libvect.GV_LINE: {'label': 'line', 'obj': Line},
  1293. libvect.GV_BOUNDARY: {'label': 'boundary', 'obj': Boundary},
  1294. libvect.GV_CENTROID: {'label': 'centroid', 'obj': Centroid},
  1295. libvect.GV_FACE: {'label': 'face', 'obj': None},
  1296. libvect.GV_KERNEL: {'label': 'kernel', 'obj': None},
  1297. libvect.GV_AREA: {'label': 'area', 'obj': Area},
  1298. libvect.GV_VOLUME: {'label': 'volume', 'obj': None}, }
  1299. GEOOBJ = {"areas": Area,
  1300. "dblinks": None,
  1301. "faces": None,
  1302. "holes": None,
  1303. "islands": Isle,
  1304. "kernels": None,
  1305. "line_points": None,
  1306. "points": Point,
  1307. "lines": Line,
  1308. "nodes": Node,
  1309. "volumes": None}
  1310. def c_read_next_line(c_mapinfo, c_points, c_cats):
  1311. v_id = c_mapinfo.contents.next_line
  1312. v_id = v_id if v_id != 0 else None
  1313. ftype = libvect.Vect_read_next_line(c_mapinfo, c_points, c_cats)
  1314. if ftype == -2:
  1315. raise StopIteration()
  1316. if ftype == -1:
  1317. raise
  1318. return ftype, v_id, c_points, c_cats
  1319. def read_next_line(c_mapinfo, table=None, writable=False,
  1320. c_points=None, c_cats=None, is2D=True):
  1321. """Return the next geometry feature of a vector map."""
  1322. c_points = c_points if c_points else ctypes.pointer(libvect.line_pnts())
  1323. c_cats = c_cats if c_cats else ctypes.pointer(libvect.line_cats())
  1324. ftype, v_id, c_points, c_cats = c_read_next_line(c_mapinfo, c_points,
  1325. c_cats)
  1326. return GV_TYPE[ftype]['obj'](v_id=v_id, c_mapinfo=c_mapinfo,
  1327. c_points=c_points, c_cats=c_cats,
  1328. table=table, writable=writable, is2D=is2D)
  1329. def c_read_line(feature_id, c_mapinfo, c_points, c_cats):
  1330. nmax = libvect.Vect_get_num_lines(c_mapinfo)
  1331. if feature_id < 0: # Handle negative indices
  1332. feature_id += nmax + 1
  1333. if feature_id > nmax:
  1334. raise IndexError('Index out of range')
  1335. if feature_id > 0:
  1336. ftype = libvect.Vect_read_line(c_mapinfo, c_points, c_cats, feature_id)
  1337. return feature_id, ftype, c_points, c_cats
  1338. else:
  1339. raise ValueError('The index must be >0, %r given.' % feature_id)
  1340. def read_line(feature_id, c_mapinfo, table=None, writable=False,
  1341. c_points=None, c_cats=None, is2D=True):
  1342. """Return a geometry object given the feature id and the c_mapinfo.
  1343. """
  1344. c_points = c_points if c_points else ctypes.pointer(libvect.line_pnts())
  1345. c_cats = c_cats if c_cats else ctypes.pointer(libvect.line_cats())
  1346. feature_id, ftype, c_points, c_cats = c_read_line(feature_id, c_mapinfo,
  1347. c_points, c_cats)
  1348. if GV_TYPE[ftype]['obj'] is not None:
  1349. return GV_TYPE[ftype]['obj'](v_id=feature_id, c_mapinfo=c_mapinfo,
  1350. c_points=c_points, c_cats=c_cats,
  1351. table=table, writable=writable, is2D=is2D)