geometry.py 55 KB

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