12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886 |
- # -*- coding: utf-8 -*-
- """
- Created on Wed Jul 18 10:46:25 2012
- @author: pietro
- """
- import ctypes
- import re
- from collections import namedtuple
- import numpy as np
- import grass.lib.gis as libgis
- import grass.lib.vector as libvect
- from grass.pygrass.utils import decode
- from grass.pygrass.errors import GrassError, mapinfo_must_be_set
- from grass.pygrass.vector.basic import Ilist, Bbox, Cats
- from grass.pygrass.vector import sql
- # For test purposes
- test_vector_name = "geometry_doctest_map"
- LineDist = namedtuple('LineDist', 'point dist spdist sldist')
- WKT = {'POINT\((.*)\)': 'point', # 'POINT\(\s*([+-]*\d+\.*\d*)+\s*\)'
- 'LINESTRING\((.*)\)': 'line'}
- def read_WKT(string):
- """Read the string and return a geometry object
- **WKT**:
- ::
- POINT(0 0)
- LINESTRING(0 0,1 1,1 2)
- POLYGON((0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1))
- MULTIPOINT(0 0,1 2)
- MULTILINESTRING((0 0,1 1,1 2),(2 3,3 2,5 4))
- MULTIPOLYGON(((0 0,4 0,4 4,0 4,0 0),(1 1,2 1,2 2,1 2,1 1)),
- ((-1 -1,-1 -2,-2 -2,-2 -1,-1 -1)))
- GEOMETRYCOLLECTION(POINT(2 3),LINESTRING(2 3,3 4))
- **EWKT**:
- ::
- POINT(0 0 0) -- XYZ
- SRID=32632;POINT(0 0) -- XY with SRID
- POINTM(0 0 0) -- XYM
- POINT(0 0 0 0) -- XYZM
- SRID=4326;MULTIPOINTM(0 0 0,1 2 1) -- XYM with SRID
- MULTILINESTRING((0 0 0,1 1 0,1 2 1),(2 3 1,3 2 1,5 4 1))
- 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))
- MULTIPOLYGON(((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)),
- ((-1 -1 0,-1 -2 0,-2 -2 0,-2 -1 0,-1 -1 0)))
- GEOMETRYCOLLECTIONM( POINTM(2 3 9), LINESTRINGM(2 3 4, 3 4 5) )
- MULTICURVE( (0 0, 5 5), CIRCULARSTRING(4 0, 4 4, 8 4) )
- POLYHEDRALSURFACE( ((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)),
- ((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)),
- ((0 0 0, 1 0 0, 1 0 1, 0 0 1, 0 0 0)),
- ((1 1 0, 1 1 1, 1 0 1, 1 0 0, 1 1 0)),
- ((0 1 0, 0 1 1, 1 1 1, 1 1 0, 0 1 0)),
- ((0 0 1, 1 0 1, 1 1 1, 0 1 1, 0 0 1)) )
- TRIANGLE ((0 0, 0 9, 9 0, 0 0))
- 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)) )
- """
- for regexp, obj in WKT.items():
- if re.match(regexp, string):
- geo = 10
- return obj(geo)
- def read_WKB(buff):
- """Read the binary buffer and return a geometry object"""
- pass
- def intersects(lineA, lineB, with_z=False):
- """Return a list of points
- >>> lineA = Line([(0, 0), (4, 0)])
- >>> lineB = Line([(2, 2), (2, -2)])
- >>> intersects(lineA, lineB)
- Line([Point(2.000000, 0.000000)])
- """
- line = Line()
- if libvect.Vect_line_get_intersections(lineA.c_points, lineB.c_points,
- line.c_points, int(with_z)):
- return line
- else:
- return []
- #=============================================
- # GEOMETRY
- #=============================================
- def get_xyz(pnt):
- """Return a tuple with: x, y, z.
- >>> pnt = Point(0, 0)
- >>> get_xyz(pnt)
- (0.0, 0.0, 0.0)
- >>> get_xyz((1, 1))
- (1, 1, 0.0)
- >>> get_xyz((1, 1, 2))
- (1, 1, 2)
- >>> get_xyz((1, 1, 2, 2)) #doctest: +ELLIPSIS
- Traceback (most recent call last):
- ...
- ValueError: The the format of the point is not supported: (1, 1, 2, 2)
- """
- if isinstance(pnt, Point):
- if pnt.is2D:
- x, y = pnt.x, pnt.y
- z = 0.
- else:
- x, y, z = pnt.x, pnt.y, pnt.z
- else:
- if len(pnt) == 2:
- x, y = pnt
- z = 0.
- elif len(pnt) == 3:
- x, y, z = pnt
- else:
- str_error = "The the format of the point is not supported: {0!r}"
- raise ValueError(str_error.format(pnt))
- return x, y, z
- class Attrs(object):
- def __init__(self, cat, table, writeable=False):
- self._cat = None
- self.cond = ''
- self.table = table
- self.cat = cat
- self.writeable = writeable
- def _get_cat(self):
- return self._cat
- def _set_cat(self, value):
- self._cat = value
- if value:
- # update condition
- self.cond = "%s=%d" % (self.table.key, value)
- cat = property(fget=_get_cat, fset=_set_cat,
- doc="Set and obtain cat value")
- def __getitem__(self, keys):
- """Return the value stored in the attribute table.
- >>> from grass.pygrass.vector import VectorTopo
- >>> test_vect = VectorTopo(test_vector_name)
- >>> test_vect.open('r')
- >>> v1 = test_vect[1]
- >>> v1.attrs['name']
- 'point'
- >>> v1.attrs['name', 'value']
- ('point', 1.0)
- >>> test_vect.close()
- """
- sqlcode = sql.SELECT_WHERE.format(cols=(keys if np.isscalar(keys)
- else ', '.join(keys)),
- tname=self.table.name,
- condition=self.cond)
- cur = self.table.execute(sqlcode)
- results = cur.fetchone()
- if results is not None:
- return results[0] if len(results) == 1 else results
- def __setitem__(self, keys, values):
- """Set value of a given column of a table attribute.
- >>> from grass.pygrass.vector import VectorTopo
- >>> test_vect = VectorTopo(test_vector_name)
- >>> test_vect.open('r')
- >>> v1 = test_vect[1]
- >>> v1.attrs['name']
- 'point'
- >>> v1.attrs['name'] = "new_point_1"
- >>> v1.attrs['name']
- 'new_point_1'
- >>> v1.attrs['name', 'value'] = "new_point_2", 100.
- >>> v1.attrs['name', 'value']
- ('new_point_2', 100.0)
- >>> v1.attrs['name', 'value'] = "point", 1.
- >>> v1.attrs.table.conn.commit()
- >>> test_vect.close()
- """
- if self.writeable:
- if np.isscalar(keys):
- keys, values = (keys, ), (values, )
- # check if key is a column of the table or not
- for key in keys:
- if key not in self.table.columns:
- raise KeyError('Column: %s not in table' % key)
- # prepare the string using as paramstyle: qmark
- vals = ','.join(['%s=?' % k for k in keys])
- # "UPDATE {tname} SET {values} WHERE {condition};"
- sqlcode = sql.UPDATE_WHERE.format(tname=self.table.name,
- values=vals,
- condition=self.cond)
- self.table.execute(sqlcode, values=values)
- #self.table.conn.commit()
- else:
- str_err = "You can only read the attributes if the map is in another mapset"
- raise GrassError(str_err)
- def __dict__(self):
- """Return a dict of the attribute table row."""
- dic = {}
- for key, val in zip(self.keys(), self.values()):
- dic[key] = val
- return dic
- def values(self):
- """Return the values of the attribute table row.
- >>> from grass.pygrass.vector import VectorTopo
- >>> test_vect = VectorTopo(test_vector_name)
- >>> test_vect.open('r')
- >>> v1 = test_vect[1]
- >>> v1.attrs.values()
- (1, 'point', 1.0)
- >>> test_vect.close()
- """
- #SELECT {cols} FROM {tname} WHERE {condition}
- cur = self.table.execute(sql.SELECT_WHERE.format(cols='*',
- tname=self.table.name,
- condition=self.cond))
- return cur.fetchone()
- def keys(self):
- """Return the column name of the attribute table.
- >>> from grass.pygrass.vector import VectorTopo
- >>> test_vect = VectorTopo(test_vector_name)
- >>> test_vect.open('r')
- >>> v1 = test_vect[1]
- >>> v1.attrs.keys()
- ['cat', 'name', 'value']
- >>> test_vect.close()
- """
- return self.table.columns.names()
- def commit(self):
- """Save the changes"""
- self.table.conn.commit()
- class Geo(object):
- """
- Base object for different feature types
- """
- gtype = None
- def __init__(self, v_id=0, c_mapinfo=None, c_points=None, c_cats=None,
- table=None, writeable=False, is2D=True, free_points=False,
- free_cats=False):
- """Constructor of a geometry object
- :param v_id: The vector feature id
- :param c_mapinfo: A pointer to the vector mapinfo structure
- :param c_points: A pointer to a libvect.line_pnts structure, this
- is optional, if not set an internal structure will
- be allocated and free'd at object destruction
- :param c_cats: A pointer to a libvect.line_cats structure, this
- is optional, if not set an internal structure will
- be allocated and free'd at object destruction
- :param table: The attribute table to select attributes for
- this feature
- :param writeable: Not sure what this is for?
- :param is2D: If True this feature has two dimensions, False if
- this feature has three dimensions
- :param free_points: Set this True if the provided c_points structure
- should be free'd at object destruction, be aware
- that no other object should free them, otherwise
- you can expect a double free corruption segfault
- :param free_cats: Set this True if the provided c_cats structure
- should be free'd at object destruction, be aware
- that no other object should free them, otherwise
- you can expect a double free corruption segfault
- """
- self.id = v_id # vector id
- self.c_mapinfo = c_mapinfo
- self.is2D = (is2D if is2D is not None else
- bool(libvect.Vect_is_3d(self.c_mapinfo) != 1))
- # Set True if cats and points are allocated by this object
- # to free the cats and points structures on destruction
- self._free_points = False
- self._free_cats = False
- read = False
- # set c_points
- if c_points is None:
- self.c_points = ctypes.pointer(libvect.line_pnts())
- self._free_points = True
- read = True
- else:
- self.c_points = c_points
- self._free_points = free_points
- # set c_cats
- if c_cats is None:
- self.c_cats = ctypes.pointer(libvect.line_cats())
- self._free_cats = free_cats
- read = True
- else:
- self.c_cats = c_cats
- self._free_cats = True
- if self.id and self.c_mapinfo is not None and read:
- self.read()
- # set the attributes as last thing to do
- self.attrs = None
- if table is not None and self.cat is not None:
- self.attrs = Attrs(self.cat, table, writeable)
- def __del__(self):
- """Take care of the allocated line_pnts and line_cats allocation
- """
- if self._free_points == True and self.c_points:
- if self.c_points.contents.alloc_points > 0:
- #print("G_free(points) [%i]"%(self.c_points.contents.alloc_points))
- libgis.G_free(self.c_points.contents.x)
- libgis.G_free(self.c_points.contents.y)
- if self.c_points.contents.z:
- libgis.G_free(self.c_points.contents.z)
- if self._free_cats == True and self.c_cats:
- if self.c_cats.contents.alloc_cats > 0:
- #print("G_free(cats) [%i]"%(self.c_cats.contents.alloc_cats))
- libgis.G_free(self.c_cats.contents.cat)
- @property
- def cat(self):
- if self.c_cats.contents.cat:
- return self.c_cats.contents.cat.contents.value
- def has_topology(self):
- if self.c_mapinfo is not None:
- return self.c_mapinfo.contents.level == 2
- else:
- return False
- @mapinfo_must_be_set
- def read(self):
- """Read and set the coordinates of the centroid from the vector map,
- using the centroid_id and calling the Vect_read_line C function"""
- self.id, ftype, c_points, c_cats = c_read_line(self.id, self.c_mapinfo,
- self.c_points,
- self.c_cats)
- def to_wkt(self):
- """Return a "well know text" (WKT) geometry string, this method uses
- the GEOS implementation in the vector library. ::
- >>> pnt = Point(10, 100)
- >>> pnt.to_wkt()
- 'POINT (10.0000000000000000 100.0000000000000000)'
- """
- return decode(libvect.Vect_line_to_wkt(self.c_points, self.gtype, not self.is2D))
- def to_wkb(self):
- """Return a "well know binary" (WKB) geometry byte array, this method uses
- the GEOS implementation in the vector library. ::
- >>> pnt = Point(10, 100)
- >>> wkb = pnt.to_wkb()
- >>> len(wkb)
- 21
- """
- size = ctypes.c_size_t()
- barray = libvect.Vect_line_to_wkb(self.c_points, self.gtype,
- not self.is2D, ctypes.byref(size))
- return(ctypes.string_at(barray, size.value))
- class Point(Geo):
- """Instantiate a Point object that could be 2 or 3D, default
- parameters are 0.
- ::
- >>> pnt = Point()
- >>> pnt.x
- 0.0
- >>> pnt.y
- 0.0
- >>> pnt.z
- >>> pnt.is2D
- True
- >>> pnt
- Point(0.000000, 0.000000)
- >>> pnt.z = 0
- >>> pnt.is2D
- False
- >>> pnt
- Point(0.000000, 0.000000, 0.000000)
- >>> print(pnt)
- POINT Z (0.0000000000000000 0.0000000000000000 0.0000000000000000)
- >>> c_points = ctypes.pointer(libvect.line_pnts())
- >>> c_cats = ctypes.pointer(libvect.line_cats())
- >>> p = Point(c_points = c_points, c_cats=c_cats)
- >>> del p
- >>> c_points = ctypes.pointer(libvect.line_pnts())
- >>> c_cats = ctypes.pointer(libvect.line_cats())
- >>> p = Point(c_points=c_points, c_cats=c_cats, free_points=True,
- ... free_cats=True)
- >>> del p
- ..
- """
- # geometry type
- gtype = libvect.GV_POINT
- def __init__(self, x=0, y=0, z=None, **kargs):
- super(Point, self).__init__(**kargs)
- if self.id and self.c_mapinfo:
- self.read()
- else:
- self.is2D = True if z is None else False
- z = z if z is not None else 0
- libvect.Vect_append_point(self.c_points, x, y, z)
- def _get_x(self):
- return self.c_points.contents.x[0]
- def _set_x(self, value):
- self.c_points.contents.x[0] = value
- x = property(fget=_get_x, fset=_set_x,
- doc="Set and obtain x coordinate")
- def _get_y(self):
- return self.c_points.contents.y[0]
- def _set_y(self, value):
- self.c_points.contents.y[0] = value
- y = property(fget=_get_y, fset=_set_y,
- doc="Set and obtain y coordinate")
- def _get_z(self):
- if self.is2D:
- return None
- return self.c_points.contents.z[0]
- def _set_z(self, value):
- if value is None:
- self.is2D = True
- self.c_points.contents.z[0] = 0
- else:
- self.c_points.contents.z[0] = value
- self.is2D = False
- z = property(fget=_get_z, fset=_set_z,
- doc="Set and obtain z coordinate")
- def __str__(self):
- return self.to_wkt()
- def __repr__(self):
- return "Point(%s)" % ', '.join(['%f' % coor for coor in self.coords()])
- def __eq__(self, pnt):
- """Return True if the coordinates are the same.
- >>> p0 = Point()
- >>> p1 = Point()
- >>> p2 = Point(1, 1)
- >>> p0 == p1
- True
- >>> p1 == p2
- False
- """
- if isinstance(pnt, Point):
- return pnt.coords() == self.coords()
- return Point(*pnt).coords() == self.coords()
- def __ne__(self, other):
- return not self == other
- # Restore Python 2 hashing beaviour on Python 3
- __hash__ = object.__hash__
- def coords(self):
- """Return a tuple with the point coordinates. ::
- >>> pnt = Point(10, 100)
- >>> pnt.coords()
- (10.0, 100.0)
- If the point is 2D return a x, y tuple. But if we change the ``z``
- the Point object become a 3D point, therefore the method return a
- x, y, z tuple. ::
- >>> pnt.z = 1000.
- >>> pnt.coords()
- (10.0, 100.0, 1000.0)
- ..
- """
- if self.is2D:
- return self.x, self.y
- else:
- return self.x, self.y, self.z
- def to_wkt_p(self):
- """Return a "well know text" (WKT) geometry string Python implementation. ::
- >>> pnt = Point(10, 100)
- >>> pnt.to_wkt_p()
- 'POINT(10.000000 100.000000)'
- .. warning::
- Only ``POINT`` (2/3D) are supported, ``POINTM`` and ``POINT`` with:
- ``XYZM`` are not supported yet.
- """
- return "POINT(%s)" % ' '.join(['%f' % coord
- for coord in self.coords()])
- def distance(self, pnt):
- """Calculate distance of 2 points, using the Vect_points_distance
- C function, If one of the point have z == None, return the 2D distance.
- :param pnt: the point for calculate the distance
- :type pnt: a Point object or a tuple with the coordinates
- >>> pnt0 = Point(0, 0, 0)
- >>> pnt1 = Point(1, 0)
- >>> pnt0.distance(pnt1)
- 1.0
- >>> pnt1.z = 1
- >>> pnt1
- Point(1.000000, 0.000000, 1.000000)
- >>> pnt0.distance(pnt1)
- 1.4142135623730951
- """
- if self.is2D or pnt.is2D:
- return libvect.Vect_points_distance(self.x, self.y, 0,
- pnt.x, pnt.y, 0, 0)
- else:
- return libvect.Vect_points_distance(self.x, self.y, self.z,
- pnt.x, pnt.y, pnt.z, 1)
- def buffer(self, dist=None, dist_x=None, dist_y=None, angle=0,
- round_=True, tol=0.1):
- """Return the buffer area around the point, using the
- ``Vect_point_buffer2`` C function.
- :param dist: the distance around the point
- :type dist: num
- :param dist_x: the distance along x
- :type dist_x: num
- :param dist_y: the distance along y
- :type dist_y: num
- :param angle: the angle between 0x and major axis
- :type angle: num
- :param round_: to make corners round
- :type round_: bool
- :param tol: fix the maximum distance between theoretical arc and
- output segments
- :type tol: float
- :returns: the buffer as Area object
- >>> pnt = Point(0, 0)
- >>> boundary, centroid = pnt.buffer(10)
- >>> boundary #doctest: +ELLIPSIS
- Line([Point(10.000000, 0.000000),...Point(10.000000, 0.000000)])
- >>> centroid
- Point(0.000000, 0.000000)
- """
- if dist is not None:
- dist_x = dist
- dist_y = dist
- elif not dist_x or not dist_y:
- raise TypeError('TypeError: buffer expected 1 arguments, got 0')
- bound = Line()
- p_points = ctypes.pointer(bound.c_points)
- libvect.Vect_point_buffer2(self.x, self.y,
- dist_x, dist_y,
- angle, int(round_), tol,
- p_points)
- return (bound, self)
- class Line(Geo):
- """Instantiate a new Line with a list of tuple, or with a list of Point. ::
- >>> line = Line([(0, 0), (1, 1), (2, 0), (1, -1)])
- >>> line #doctest: +NORMALIZE_WHITESPACE
- Line([Point(0.000000, 0.000000),
- Point(1.000000, 1.000000),
- Point(2.000000, 0.000000),
- Point(1.000000, -1.000000)])
- ..
- """
- # geometry type
- gtype = libvect.GV_LINE
- def __init__(self, points=None, **kargs):
- super(Line, self).__init__(**kargs)
- if points is not None:
- for pnt in points:
- self.append(pnt)
- def __getitem__(self, key):
- """Get line point of given index, slice allowed. ::
- >>> line = Line([(0, 0), (1, 1), (2, 2), (3, 3)])
- >>> line[1]
- Point(1.000000, 1.000000)
- >>> line[-1]
- Point(3.000000, 3.000000)
- >>> line[:2]
- [Point(0.000000, 0.000000), Point(1.000000, 1.000000)]
- ..
- """
- #TODO:
- # line[0].x = 10 is not working
- #pnt.c_px = ctypes.pointer(self.c_points.contents.x[indx])
- # pnt.c_px = ctypes.cast(id(self.c_points.contents.x[indx]),
- # ctypes.POINTER(ctypes.c_double))
- if isinstance(key, slice):
- #import pdb; pdb.set_trace()
- #Get the start, stop, and step from the slice
- return [Point(self.c_points.contents.x[indx],
- self.c_points.contents.y[indx],
- None if self.is2D else self.c_points.contents.z[indx])
- for indx in range(*key.indices(len(self)))]
- elif isinstance(key, int):
- if key < 0: # Handle negative indices
- key += self.c_points.contents.n_points
- if key >= self.c_points.contents.n_points:
- raise IndexError('Index out of range')
- return Point(self.c_points.contents.x[key],
- self.c_points.contents.y[key],
- None if self.is2D else self.c_points.contents.z[key])
- else:
- raise ValueError("Invalid argument type: %r." % key)
- def __setitem__(self, indx, pnt):
- """Change the coordinate of point. ::
- >>> line = Line([(0, 0), (1, 1)])
- >>> line[0] = (2, 2)
- >>> line
- Line([Point(2.000000, 2.000000), Point(1.000000, 1.000000)])
- ..
- """
- x, y, z = get_xyz(pnt)
- self.c_points.contents.x[indx] = x
- self.c_points.contents.y[indx] = y
- self.c_points.contents.z[indx] = z
- def __iter__(self):
- """Return a Point generator of the Line"""
- return (self.__getitem__(i) for i in range(self.__len__()))
- def __len__(self):
- """Return the number of points of the line."""
- return self.c_points.contents.n_points
- def __str__(self):
- return self.to_wkt()
- def __repr__(self):
- return "Line([%s])" % ', '.join([repr(pnt) for pnt in self.__iter__()])
- def point_on_line(self, distance, angle=0, slope=0):
- """Return a Point object on line in the specified distance, using the
- `Vect_point_on_line` C function.
- Raise a ValueError If the distance exceed the Line length. ::
- >>> line = Line([(0, 0), (1, 1)])
- >>> line.point_on_line(5) #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
- Traceback (most recent call last):
- ...
- ValueError: The distance exceed the length of the line,
- that is: 1.414214
- >>> line.point_on_line(1)
- Point(0.707107, 0.707107)
- ..
- """
- # instantiate an empty Point object
- maxdist = self.length()
- if distance > maxdist:
- str_err = "The distance exceed the length of the line, that is: %f"
- raise ValueError(str_err % maxdist)
- pnt = Point(0, 0, -9999)
- if not libvect.Vect_point_on_line(self.c_points, distance,
- pnt.c_points.contents.x,
- pnt.c_points.contents.y,
- pnt.c_points.contents.z,
- ctypes.pointer(ctypes.c_double(angle)),
- ctypes.pointer(ctypes.c_double(slope))):
- raise ValueError("Vect_point_on_line give an error.")
- pnt.is2D = self.is2D
- return pnt
- @mapinfo_must_be_set
- def alive(self):
- """Return True if this line is alive or False if this line is
- dead or its index is out of range.
- """
- return(bool(libvect.Vect_line_alive(self.c_mapinfo, self.id)))
- def append(self, pnt):
- """Appends one point to the end of a line, using the
- ``Vect_append_point`` C function.
- :param pnt: the point to add to line
- :type pnt: a Point object or a tuple with the coordinates
- >>> line = Line()
- >>> line.append((10, 100))
- >>> line
- Line([Point(10.000000, 100.000000)])
- >>> line.append((20, 200))
- >>> line
- Line([Point(10.000000, 100.000000), Point(20.000000, 200.000000)])
- Like python list.
- """
- x, y, z = get_xyz(pnt)
- libvect.Vect_append_point(self.c_points, x, y, z)
- def bbox(self, bbox=None):
- """Return the bounding box of the line, using ``Vect_line_box``
- C function. ::
- >>> line = Line([(0, 0), (0, 1), (2, 1), (2, 0)])
- >>> bbox = line.bbox()
- >>> bbox
- Bbox(1.0, 0.0, 2.0, 0.0)
- ..
- """
- bbox = bbox if bbox else Bbox()
- libvect.Vect_line_box(self.c_points, bbox.c_bbox)
- return bbox
- def extend(self, line, forward=True):
- """Appends points to the end of a line.
- :param line: it is possible to extend a line, give a list of points,
- or directly with a line_pnts struct.
- :type line: Line object ot list of points
- :param forward: if forward is True the line is extend forward otherwise
- is extend backward. The method use the
- `Vect_append_points` C function.
- :type forward: bool
- >>> line = Line([(0, 0), (1, 1)])
- >>> line.extend( Line([(2, 2), (3, 3)]) )
- >>> line #doctest: +NORMALIZE_WHITESPACE
- Line([Point(0.000000, 0.000000),
- Point(1.000000, 1.000000),
- Point(2.000000, 2.000000),
- Point(3.000000, 3.000000)])
- """
- # set direction
- if forward:
- direction = libvect.GV_FORWARD
- else:
- direction = libvect.GV_BACKWARD
- # check if is a Line object
- if isinstance(line, Line):
- c_points = line.c_points
- else:
- # instantiate a Line object
- lin = Line()
- for pnt in line:
- # add the points to the line
- lin.append(pnt)
- c_points = lin.c_points
- libvect.Vect_append_points(self.c_points, c_points, direction)
- def insert(self, indx, pnt):
- """Insert new point at index position and move all old points at
- that position and above up, using ``Vect_line_insert_point``
- C function.
- :param indx: the index where add new point
- :type indx: int
- :param pnt: the point to add
- :type pnt: a Point object
- >>> line = Line([(0, 0), (1, 1)])
- >>> line.insert(0, Point(1.000000, -1.000000) )
- >>> line #doctest: +NORMALIZE_WHITESPACE
- Line([Point(1.000000, -1.000000),
- Point(0.000000, 0.000000),
- Point(1.000000, 1.000000)])
- """
- if indx < 0: # Handle negative indices
- indx += self.c_points.contents.n_points
- if indx >= self.c_points.contents.n_points:
- raise IndexError('Index out of range')
- x, y, z = get_xyz(pnt)
- libvect.Vect_line_insert_point(self.c_points, indx, x, y, z)
- def length(self):
- """Calculate line length, 3D-length in case of 3D vector line, using
- `Vect_line_length` C function. ::
- >>> line = Line([(0, 0), (1, 1), (0, 1)])
- >>> line.length()
- 2.414213562373095
- ..
- """
- return libvect.Vect_line_length(self.c_points)
- def length_geodesic(self):
- """Calculate line length, usig `Vect_line_geodesic_length` C function.
- ::
- >>> line = Line([(0, 0), (1, 1), (0, 1)])
- >>> line.length_geodesic()
- 2.414213562373095
- ..
- """
- return libvect.Vect_line_geodesic_length(self.c_points)
- def distance(self, pnt):
- """Calculate the distance between line and a point.
- :param pnt: the point to calculate distance
- :type pnt: a Point object or a tuple with the coordinates
- Return a namedtuple with:
- * point: the closest point on the line,
- * dist: the distance between these two points,
- * spdist: distance to point on line from segment beginning
- * sldist: distance to point on line form line beginning along line
- The distance is compute using the ``Vect_line_distance`` C function.
- >>> point = Point(2.3, 0.5)
- >>> line = Line([(0, 0), (2, 0), (3, 0)])
- >>> line.distance(point) #doctest: +NORMALIZE_WHITESPACE
- LineDist(point=Point(2.300000, 0.000000),
- dist=0.5, spdist=0.2999999999999998, sldist=2.3)
- """
- # instantite outputs
- cx = ctypes.c_double(0)
- cy = ctypes.c_double(0)
- cz = ctypes.c_double(0)
- dist = ctypes.c_double(0)
- sp_dist = ctypes.c_double(0)
- lp_dist = ctypes.c_double(0)
- libvect.Vect_line_distance(self.c_points,
- pnt.x, pnt.y, 0 if pnt.is2D else pnt.z,
- 0 if self.is2D else 1,
- ctypes.byref(cx), ctypes.byref(cy),
- ctypes.byref(cz), ctypes.byref(dist),
- ctypes.byref(sp_dist),
- ctypes.byref(lp_dist))
- # instantiate the Point class
- point = Point(cx.value, cy.value, cz.value)
- point.is2D = self.is2D
- return LineDist(point, dist.value, sp_dist.value, lp_dist.value)
- @mapinfo_must_be_set
- def first_cat(self):
- """Fetches FIRST category number for given vector line and field, using
- the ``Vect_get_line_cat`` C function.
- .. warning::
- Not implemented yet.
- """
- # TODO: add this method.
- # libvect.Vect_get_line_cat(self.c_mapinfo, self.id, self.field)
- pass
- def pop(self, indx):
- """Return the point in the index position and remove from the Line.
- :param indx: the index where add new point
- :type indx: int
- >>> line = Line([(0, 0), (1, 1), (2, 2)])
- >>> midle_pnt = line.pop(1)
- >>> midle_pnt #doctest: +NORMALIZE_WHITESPACE
- Point(1.000000, 1.000000)
- >>> line #doctest: +NORMALIZE_WHITESPACE
- Line([Point(0.000000, 0.000000), Point(2.000000, 2.000000)])
- """
- if indx < 0: # Handle negative indices
- indx += self.c_points.contents.n_points
- if indx >= self.c_points.contents.n_points:
- raise IndexError('Index out of range')
- pnt = self.__getitem__(indx)
- libvect.Vect_line_delete_point(self.c_points, indx)
- return pnt
- def delete(self, indx):
- """Remove the point in the index position.
- :param indx: the index where add new point
- :type indx: int
- >>> line = Line([(0, 0), (1, 1), (2, 2)])
- >>> line.delete(-1)
- >>> line #doctest: +NORMALIZE_WHITESPACE
- Line([Point(0.000000, 0.000000), Point(1.000000, 1.000000)])
- """
- if indx < 0: # Handle negative indices
- indx += self.c_points.contents.n_points
- if indx >= self.c_points.contents.n_points:
- raise IndexError('Index out of range')
- libvect.Vect_line_delete_point(self.c_points, indx)
- def prune(self):
- """Remove duplicate points, i.e. zero length segments, using
- `Vect_line_prune` C function. ::
- >>> line = Line([(0, 0), (1, 1), (1, 1), (2, 2)])
- >>> line.prune()
- >>> line #doctest: +NORMALIZE_WHITESPACE
- Line([Point(0.000000, 0.000000),
- Point(1.000000, 1.000000),
- Point(2.000000, 2.000000)])
- ..
- """
- libvect.Vect_line_prune(self.c_points)
- def prune_thresh(self, threshold):
- """Remove points in threshold, using the ``Vect_line_prune_thresh``
- C function.
- :param threshold: the threshold value where prune points
- :type threshold: num
- >>> line = Line([(0, 0), (1.0, 1.0), (1.2, 0.9), (2, 2)])
- >>> line.prune_thresh(0.5)
- >>> line #doctest: +SKIP +NORMALIZE_WHITESPACE
- Line([Point(0.000000, 0.000000),
- Point(1.000000, 1.000000),
- Point(2.000000, 2.000000)])
- .. warning ::
- prune_thresh is not working yet.
- """
- libvect.Vect_line_prune(self.c_points, ctypes.c_double(threshold))
- def remove(self, pnt):
- """Delete point at given index and move all points above down, using
- `Vect_line_delete_point` C function.
- :param pnt: the point to remove
- :type pnt: a Point object or a tuple with the coordinates
- >>> line = Line([(0, 0), (1, 1), (2, 2)])
- >>> line.remove((2, 2))
- >>> line[-1] #doctest: +NORMALIZE_WHITESPACE
- Point(1.000000, 1.000000)
- ..
- """
- for indx, point in enumerate(self.__iter__()):
- if pnt == point:
- libvect.Vect_line_delete_point(self.c_points, indx)
- return
- raise ValueError('list.remove(x): x not in list')
- def reverse(self):
- """Reverse the order of vertices, using `Vect_line_reverse`
- C function. ::
- >>> line = Line([(0, 0), (1, 1), (2, 2)])
- >>> line.reverse()
- >>> line #doctest: +NORMALIZE_WHITESPACE
- Line([Point(2.000000, 2.000000),
- Point(1.000000, 1.000000),
- Point(0.000000, 0.000000)])
- ..
- """
- libvect.Vect_line_reverse(self.c_points)
- def segment(self, start, end):
- """Create line segment. using the ``Vect_line_segment`` C function.
- :param start: distance from the beginning of the line where
- the segment start
- :type start: float
- :param end: distance from the beginning of the line where
- the segment end
- :type end: float
- ::
- # x (1, 1)
- # |
- # |-
- # |
- # x--------x (1, 0)
- # (0, 0) ^
- >>> line = Line([(0, 0), (1, 0), (1, 1)])
- >>> line.segment(0.5, 1.5) #doctest: +NORMALIZE_WHITESPACE
- Line([Point(0.500000, 0.000000),
- Point(1.000000, 0.000000),
- Point(1.000000, 0.500000)])
- """
- line = Line()
- libvect.Vect_line_segment(self.c_points, start, end, line.c_points)
- return line
- def to_list(self):
- """Return a list of tuple. ::
- >>> line = Line([(0, 0), (1, 1), (2, 0), (1, -1)])
- >>> line.to_list()
- [(0.0, 0.0), (1.0, 1.0), (2.0, 0.0), (1.0, -1.0)]
- ..
- """
- return [pnt.coords() for pnt in self.__iter__()]
- def to_array(self):
- """Return an array of coordinates. ::
- >>> line = Line([(0, 0), (1, 1), (2, 0), (1, -1)])
- >>> line.to_array() #doctest: +NORMALIZE_WHITESPACE
- array([[ 0., 0.],
- [ 1., 1.],
- [ 2., 0.],
- [ 1., -1.]])
- ..
- """
- return np.array(self.to_list())
- def to_wkt_p(self):
- """Return a Well Known Text string of the line. ::
- >>> line = Line([(0, 0), (1, 1), (1, 2)])
- >>> line.to_wkt_p() #doctest: +ELLIPSIS
- 'LINESTRING(0.000000 0.000000, ..., 1.000000 2.000000)'
- ..
- """
- return "LINESTRING(%s)" % ', '.join([
- ' '.join(['%f' % coord for coord in pnt.coords()])
- for pnt in self.__iter__()])
- def from_wkt(self, wkt):
- """Create a line reading a WKT string.
- :param wkt: the WKT string containing the LINESTRING
- :type wkt: str
- >>> line = Line()
- >>> line.from_wkt("LINESTRING(0 0,1 1,1 2)")
- >>> line #doctest: +NORMALIZE_WHITESPACE
- Line([Point(0.000000, 0.000000),
- Point(1.000000, 1.000000),
- Point(1.000000, 2.000000)])
- ..
- """
- match = re.match('LINESTRING\((.*)\)', wkt)
- if match:
- self.reset()
- for coord in match.groups()[0].strip().split(','):
- self.append(tuple([float(e) for e in coord.split(' ')]))
- else:
- return None
- def buffer(self, dist=None, dist_x=None, dist_y=None,
- angle=0, round_=True, caps=True, tol=0.1):
- """Return the buffer area around the line, using the
- ``Vect_line_buffer2`` C function.
- :param dist: the distance around the line
- :type dist: num
- :param dist_x: the distance along x
- :type dist_x: num
- :param dist_y: the distance along y
- :type dist_y: num
- :param angle: the angle between 0x and major axis
- :type angle: num
- :param round_: to make corners round
- :type round_: bool
- :param tol: fix the maximum distance between theoretical arc and
- output segments
- :type tol: float
- :returns: the buffer as Area object
- >>> line = Line([(0, 0), (0, 2)])
- >>> boundary, centroid, isles = line.buffer(10)
- >>> boundary #doctest: +ELLIPSIS
- Line([Point(-10.000000, 0.000000),...Point(-10.000000, 0.000000)])
- >>> centroid #doctest: +NORMALIZE_WHITESPACE
- Point(0.000000, 0.000000)
- >>> isles
- []
- ..
- """
- if dist is not None:
- dist_x = dist
- dist_y = dist
- elif not dist_x or not dist_y:
- raise TypeError('TypeError: buffer expected 1 arguments, got 0')
- p_bound = ctypes.pointer(ctypes.pointer(libvect.line_pnts()))
- pp_isle = ctypes.pointer(ctypes.pointer(
- ctypes.pointer(libvect.line_pnts())))
- n_isles = ctypes.pointer(ctypes.c_int())
- libvect.Vect_line_buffer2(self.c_points,
- dist_x, dist_y, angle,
- int(round_), int(caps), tol,
- p_bound, pp_isle, n_isles)
- boundary = Line(c_points=p_bound.contents)
- isles = [Line(c_points=pp_isle[i].contents)
- for i in range(n_isles.contents.value) if pp_isle[i]]
- return(boundary, self[0], isles)
- def reset(self):
- """Reset line, using `Vect_reset_line` C function. ::
- >>> line = Line([(0, 0), (1, 1), (2, 0), (1, -1)])
- >>> len(line)
- 4
- >>> line.reset()
- >>> len(line)
- 0
- >>> line
- Line([])
- ..
- """
- libvect.Vect_reset_line(self.c_points)
- @mapinfo_must_be_set
- def nodes(self):
- """Return the start and end nodes of the line
- This method requires topology build.
- return: A tuple of Node objects that represent the
- start and end point of this line.
- """
- if self.has_topology():
- n1 = ctypes.c_int()
- n2 = ctypes.c_int()
- libvect.Vect_get_line_nodes(self.c_mapinfo, self.id,
- ctypes.byref(n1),
- ctypes.byref(n2))
- return (Node(n1.value, self.c_mapinfo),
- Node(n2.value, self.c_mapinfo))
- class Node(object):
- """Node class for topological analysis of line neighbors.
- Objects of this class will be returned by the node() function
- of a Line object.
- All methods in this class require a proper setup of the Node
- objects. Hence, the correct id and a valid pointer to a mapinfo
- object must be provided in the constructions. Otherwise a segfault
- may happen.
- """
- def __init__(self, v_id, c_mapinfo, **kwords):
- """Construct a Node object
- param v_id: The unique node id
- param c_mapinfo: A valid pointer to the mapinfo object
- param **kwords: Ignored
- """
- self.id = v_id # vector id
- self.c_mapinfo = c_mapinfo
- self._setup()
- @mapinfo_must_be_set
- def _setup(self):
- self.is2D = bool(libvect.Vect_is_3d(self.c_mapinfo) != 1)
- self.nlines = libvect.Vect_get_node_n_lines(self.c_mapinfo, self.id)
- def __len__(self):
- return self.nlines
- def __iter__(self):
- return self.ilines()
- def __repr__(self):
- return "Node(%d)" % self.id
- @mapinfo_must_be_set
- def alive(self):
- """Return True if this node is alive or False if this node is
- dead or its index is out of range.
- """
- return(bool(libvect.Vect_node_alive(self.c_mapinfo, self.id)))
- @mapinfo_must_be_set
- def coords(self):
- """Return a tuple with the node coordinates."""
- x = ctypes.c_double()
- y = ctypes.c_double()
- z = ctypes.c_double()
- libvect.Vect_get_node_coor(self.c_mapinfo, self.id, ctypes.byref(x),
- ctypes.byref(y), ctypes.byref(z))
- return (x.value, y.value) if self.is2D else (x.value, y.value, z.value)
- def to_wkt(self):
- """Return a "well know text" (WKT) geometry string. ::
- """
- return "POINT(%s)" % ' '.join(['%f' % coord
- for coord in self.coords()])
- def to_wkb(self):
- """Return a "well know binary" (WKB) geometry array. ::
- TODO: Must be implemented
- """
- raise Exception("Not implemented")
- def ilines(self, only_in=False, only_out=False):
- """Return a generator with all lines id connected to a node.
- The line id is negative if line is ending on the node and positive if
- starting from the node.
- :param only_in: Return only the lines that are ending in the node
- :type only_in: bool
- :param only_out: Return only the lines that are starting in the node
- :type only_out: bool
- """
- for iline in range(self.nlines):
- lid = libvect.Vect_get_node_line(self.c_mapinfo, self.id, iline)
- if (not only_in and lid > 0) or (not only_out and lid < 0):
- yield lid
- @mapinfo_must_be_set
- def lines(self, only_in=False, only_out=False):
- """Return a generator with all lines connected to a node.
- :param only_in: Return only the lines that are ending in the node
- :type only_in: bool
- :param only_out: Return only the lines that are starting in the node
- :type only_out: bool
- """
- for iline in self.ilines(only_in, only_out):
- yield Line(v_id=abs(iline), c_mapinfo=self.c_mapinfo)
- @mapinfo_must_be_set
- def angles(self):
- """Return a generator with all lines angles in a node."""
- for iline in range(self.nlines):
- yield libvect.Vect_get_node_line_angle(self.c_mapinfo,
- self.id, iline)
- class Boundary(Line):
- """
- """
- # geometry type
- gtype = libvect.GV_BOUNDARY
- def __init__(self, **kargs):
- super(Boundary, self).__init__(**kargs)
- v_id = kargs.get('v_id', 0)
- # not sure what it means that v_id is None
- v_id = 0 if v_id is None else v_id
- self.dir = libvect.GV_FORWARD if v_id > 0 else libvect.GV_BACKWARD
- self.c_left = ctypes.pointer(ctypes.c_int())
- self.c_right = ctypes.pointer(ctypes.c_int())
- @property
- def left_area_id(self):
- """Left side area id, only available after read_area_ids() was called"""
- return self.c_left.contents.value
- @property
- def right_area_id(self):
- """Right side area id, only available after read_area_ids() was called"""
- return self.c_right.contents.value
- def __repr__(self):
- return "Boundary([%s])" % ', '.join([repr(pnt) for pnt in self.__iter__()])
- @mapinfo_must_be_set
- def _centroid(self, side, idonly=False):
- if side > 0:
- v_id = libvect.Vect_get_area_centroid(self.c_mapinfo, side)
- v_id = v_id if v_id else None
- if idonly:
- return v_id
- else:
- cntr = Centroid(v_id=v_id, c_mapinfo=self.c_mapinfo)
- return cntr
- def left_centroid(self, idonly=False):
- """Return left centroid
- :param idonly: True to return only the cat of feature
- :type idonly: bool
- """
- return self._centroid(self.c_left.contents.value, idonly)
- def right_centroid(self, idonly=False):
- """Return right centroid
- :param idonly: True to return only the cat of feature
- :type idonly: bool
- """
- return self._centroid(self.c_right.contents.value, idonly)
- @mapinfo_must_be_set
- def read_area_ids(self):
- """Read and return left and right area ids of the boundary"""
- libvect.Vect_get_line_areas(self.c_mapinfo, self.id,
- self.c_left, self.c_right)
- return self.c_left.contents.value, self.c_right.contents.value
- def area(self):
- """Return the area of the polygon.
- >>> bound = Boundary(points=[(0, 0), (0, 2), (2, 2), (2, 0),
- ... (0, 0)])
- >>> bound.area()
- 4.0
- """
- libgis.G_begin_polygon_area_calculations()
- return libgis.G_area_of_polygon(self.c_points.contents.x,
- self.c_points.contents.y,
- self.c_points.contents.n_points)
- class Centroid(Point):
- """The Centroid class inherit from the Point class.
- Centroid contains an attribute with the C Map_info struct, and attributes
- with the id of the Area. ::
- >>> centroid = Centroid(x=0, y=10)
- >>> centroid
- Centroid(0.000000, 10.000000)
- >>> from grass.pygrass.vector import VectorTopo
- >>> test_vect = VectorTopo(test_vector_name)
- >>> test_vect.open(mode='r')
- >>> centroid = Centroid(v_id=18, c_mapinfo=test_vect.c_mapinfo)
- >>> centroid
- Centroid(3.500000, 3.500000)
- >>> test_vect.close()
- ..
- """
- # geometry type
- gtype = libvect.GV_CENTROID
- def __init__(self, area_id=None, **kargs):
- super(Centroid, self).__init__(**kargs)
- self.area_id = area_id
- if self.id and self.c_mapinfo and self.area_id is None:
- self.area_id = self._area_id()
- elif self.c_mapinfo and self.area_id and self.id is None:
- self.id = self._centroid_id()
- if self.area_id is not None:
- self.read()
- #self.c_pline = ctypes.pointer(libvect.P_line()) if topology else None
- def __repr__(self):
- return "Centroid(%s)" % ', '.join(['%f' % co for co in self.coords()])
- @mapinfo_must_be_set
- def _centroid_id(self):
- """Return the centroid_id, using the c_mapinfo and an area_id
- attributes of the class, and calling the Vect_get_area_centroid
- C function, if no centroid_id were found return None"""
- centroid_id = libvect.Vect_get_area_centroid(self.c_mapinfo,
- self.area_id)
- return centroid_id if centroid_id != 0 else None
- @mapinfo_must_be_set
- def _area_id(self):
- """Return the area_id, using the c_mapinfo and an centroid_id
- attributes of the class, and calling the Vect_centroid_area
- C function, if no area_id were found return None"""
- area_id = libvect.Vect_get_centroid_area(self.c_mapinfo,
- self.id)
- return area_id if area_id != 0 else None
- class Isle(Geo):
- """An Isle is an area contained by another area.
- """
- def __init__(self, **kargs):
- super(Isle, self).__init__(**kargs)
- #self.area_id = area_id
- def __repr__(self):
- return "Isle(%d)" % (self.id)
- @mapinfo_must_be_set
- def boundaries(self):
- """Return a list of boundaries"""
- ilist = Ilist()
- libvect.Vect_get_isle_boundaries(self.c_mapinfo, self.id,
- ilist.c_ilist)
- return ilist
- @mapinfo_must_be_set
- def bbox(self, bbox=None):
- """Return bounding box of Isle"""
- bbox = bbox if bbox else Bbox()
- libvect.Vect_get_isle_box(self.c_mapinfo, self.id, bbox.c_bbox)
- return bbox
- @mapinfo_must_be_set
- def points(self):
- """Return a Line object with the outer ring points"""
- line = Line()
- libvect.Vect_get_isle_points(self.c_mapinfo, self.id, line.c_points)
- return line
- def to_wkt(self):
- """Return a Well Known Text string of the isle. ::
- For now the outer ring is returned
- TODO: Implement inner rings detected from isles
- """
- line = self.points()
- return "Polygon((%s))" % ', '.join([
- ' '.join(['%f' % coord for coord in pnt])
- for pnt in line.to_list()])
- def to_wkb(self):
- """Return a "well know text" (WKB) geometry array. ::
- """
- raise Exception("Not implemented")
- @mapinfo_must_be_set
- def points_geos(self):
- """Return a Line object with the outer ring points
- """
- return libvect.Vect_get_isle_points_geos(self.c_mapinfo, self.id)
- @mapinfo_must_be_set
- def area_id(self):
- """Returns area id for isle."""
- return libvect.Vect_get_isle_area(self.c_mapinfo, self.id)
- @mapinfo_must_be_set
- def alive(self):
- """Check if isle is alive or dead (topology required)"""
- return bool(libvect.Vect_isle_alive(self.c_mapinfo, self.id))
- @mapinfo_must_be_set
- def contain_pnt(self, pnt):
- """Check if point is in area.
- :param pnt: the point to remove
- :type pnt: a Point object or a tuple with the coordinates
- """
- bbox = self.bbox()
- return bool(libvect.Vect_point_in_island(pnt.x, pnt.y,
- self.c_mapinfo, self.id,
- bbox.c_bbox.contents))
- def area(self):
- """Return the area value of an Isle"""
- border = self.points()
- return libgis.G_area_of_polygon(border.c_points.contents.x,
- border.c_points.contents.y,
- border.c_points.contents.n_points)
- def perimeter(self):
- """Return the perimeter value of an Isle.
- """
- border = self.points()
- return libvect.Vect_line_geodesic_length(border.c_points)
- class Isles(object):
- def __init__(self, c_mapinfo, area_id=None):
- self.c_mapinfo = c_mapinfo
- self.area_id = area_id
- self._isles_id = None
- self._isles = None
- if area_id:
- self._isles_id = self.isles_ids()
- self._isles = self.isles()
- @mapinfo_must_be_set
- def __len__(self):
- return libvect.Vect_get_area_num_isles(self.c_mapinfo, self.area_id)
- def __repr__(self):
- return "Isles(%r)" % self.area_id
- def __getitem__(self, key):
- if self._isles is None:
- self.isles()
- return self._isles[key]
- @mapinfo_must_be_set
- def isles_ids(self):
- """Return the id of isles"""
- return [libvect.Vect_get_area_isle(self.c_mapinfo, self.area_id, i)
- for i in range(self.__len__())]
- @mapinfo_must_be_set
- def isles(self):
- """Return isles"""
- return [Isle(v_id=isle_id, c_mapinfo=self.c_mapinfo)
- for isle_id in self._isles_id]
- class Area(Geo):
- """
- Vect_build_line_area,
- Vect_find_area,
- Vect_get_area_box,
- Vect_get_area_points_geos,
- Vect_centroid_area,
- Vect_get_isle_area,
- Vect_get_line_areas,
- Vect_get_num_areas,
- Vect_get_point_in_area,
- Vect_isle_find_area,
- Vect_point_in_area,
- Vect_point_in_area_outer_ring,
- Vect_read_area_geos,
- Vect_remove_small_areas,
- Vect_select_areas_by_box,
- Vect_select_areas_by_polygon
- """
- # geometry type
- gtype = libvect.GV_AREA
- def __init__(self, **kargs):
- super(Area, self).__init__(**kargs)
- # set the attributes
- #if self.attrs and self.cat:
- # self.attrs.cat = self.cat
- def __repr__(self):
- return "Area(%d)" % self.id if self.id else "Area( )"
- @property
- def cat(self):
- centroid = self.centroid()
- return centroid.cat if centroid else None
- @mapinfo_must_be_set
- def points(self, line=None):
- """Return a Line object with the outer ring
- :param line: a Line object to fill with info from points of area
- :type line: a Line object
- """
- line = Line() if line is None else line
- libvect.Vect_get_area_points(self.c_mapinfo, self.id, line.c_points)
- return line
- @mapinfo_must_be_set
- def centroid(self):
- """Return the centroid
- :param centroid: a Centroid object to fill with info from centroid of area
- :type centroid: a Centroid object
- """
- centroid_id = libvect.Vect_get_area_centroid(self.c_mapinfo, self.id)
- if centroid_id:
- return Centroid(v_id=centroid_id, c_mapinfo=self.c_mapinfo,
- area_id=self.id)
- @mapinfo_must_be_set
- def num_isles(self):
- return libvect.Vect_get_area_num_isles(self.c_mapinfo, self.id)
- @mapinfo_must_be_set
- def isles(self, isles=None):
- """Return a list of islands located in this area"""
- if isles is not None:
- isles.area_id = self.id
- return isles
- return Isles(self.c_mapinfo, self.id)
- @mapinfo_must_be_set
- def area(self):
- """Returns area of area without areas of isles.
- double Vect_get_area_area (const struct Map_info \*Map, int area)
- """
- return libvect.Vect_get_area_area(self.c_mapinfo, self.id)
- @mapinfo_must_be_set
- def alive(self):
- """Check if area is alive or dead (topology required)
- """
- return bool(libvect.Vect_area_alive(self.c_mapinfo, self.id))
- @mapinfo_must_be_set
- def bbox(self, bbox=None):
- """Return the Bbox of area
- :param bbox: a Bbox object to fill with info from bounding box of area
- :type bbox: a Bbox object
- """
- bbox = bbox if bbox else Bbox()
- libvect.Vect_get_area_box(self.c_mapinfo, self.id, bbox.c_bbox)
- return bbox
- @mapinfo_must_be_set
- def buffer(self, dist=None, dist_x=None, dist_y=None,
- angle=0, round_=True, caps=True, tol=0.1):
- """Return the buffer area around the area, using the
- ``Vect_area_buffer2`` C function.
- :param dist: the distance around the area
- :type dist: num
- :param dist_x: the distance along x
- :type dist_x: num
- :param dist_y: the distance along y
- :type dist_y: num
- :param angle: the angle between 0x and major axis
- :type angle: num
- :param round_: to make corners round
- :type round_: bool
- :param tol: fix the maximum distance between theoretical arc and
- output segments
- :type tol: float
- :returns: the buffer as line, centroid, isles object tuple
- """
- if dist is not None:
- dist_x = dist
- dist_y = dist
- elif not dist_x or not dist_y:
- raise TypeError('TypeError: buffer expected 1 arguments, got 0')
- p_bound = ctypes.pointer(ctypes.pointer(libvect.line_pnts()))
- pp_isle = ctypes.pointer(ctypes.pointer(
- ctypes.pointer(libvect.line_pnts())))
- n_isles = ctypes.pointer(ctypes.c_int())
- libvect.Vect_area_buffer2(self.c_mapinfo, self.id,
- dist_x, dist_y, angle,
- int(round_), int(caps), tol,
- p_bound, pp_isle, n_isles)
- return (Line(c_points=p_bound.contents),
- self.centroid,
- [Line(c_points=pp_isle[i].contents)
- for i in range(n_isles.contents.value)])
- @mapinfo_must_be_set
- def boundaries(self, ilist=False):
- """Creates list of boundaries for given area.
- int Vect_get_area_boundaries(const struct Map_info \*Map,
- int area, struct ilist \*List)
- """
- ilst = Ilist()
- libvect.Vect_get_area_boundaries(self.c_mapinfo, self.id,
- ilst.c_ilist)
- if ilist:
- return ilist
- return [Boundary(v_id=abs(v_id), c_mapinfo=self.c_mapinfo) for v_id in ilst]
- def to_wkt(self):
- """Return a "well know text" (WKT) area string, this method uses
- the GEOS implementation in the vector library. ::
- """
- return decode(libvect.Vect_read_area_to_wkt(self.c_mapinfo, self.id))
- def to_wkb(self):
- """Return a "well know binary" (WKB) area byte array, this method uses
- the GEOS implementation in the vector library. ::
- """
- size = ctypes.c_size_t()
- barray = libvect.Vect_read_area_to_wkb(self.c_mapinfo, self.id,
- ctypes.byref(size))
- return(ctypes.string_at(barray, size.value))
- @mapinfo_must_be_set
- def cats(self, cats=None):
- """Get area categories.
- :param cats: a Cats object to fill with info with area categories
- :type cats: a Cats object
- """
- cats = cats if cats else Cats()
- libvect.Vect_get_area_cats(self.c_mapinfo, self.id, cats.c_cats)
- return cats
- def get_first_cat(self):
- """Find FIRST category of given field and area.
- int Vect_get_area_cat(const struct Map_info \*Map, int area, int field)
- ..warning: Not implemented
- """
- pass
- @mapinfo_must_be_set
- def contains_point(self, point, bbox=None):
- """Check if point is in area.
- :param point: the point to analyze
- :type point: a Point object or a tuple with the coordinates
- :param bbox: the bounding box where run the analysis
- :type bbox: a Bbox object
- """
- bbox = bbox if bbox else self.bbox()
- return bool(libvect.Vect_point_in_area(point.x, point.y,
- self.c_mapinfo, self.id,
- bbox.c_bbox))
- @mapinfo_must_be_set
- def perimeter(self):
- """Calculate area perimeter.
- :return: double Vect_area_perimeter (const struct line_pnts \*Points)
- """
- border = self.points()
- return libvect.Vect_line_geodesic_length(border.c_points)
- def read(self):
- pass
- #
- # Define a dictionary to convert the feature type to name and or object
- #
- GV_TYPE = {libvect.GV_POINT: {'label': 'point', 'obj': Point},
- libvect.GV_LINE: {'label': 'line', 'obj': Line},
- libvect.GV_BOUNDARY: {'label': 'boundary', 'obj': Boundary},
- libvect.GV_CENTROID: {'label': 'centroid', 'obj': Centroid},
- libvect.GV_FACE: {'label': 'face', 'obj': None},
- libvect.GV_KERNEL: {'label': 'kernel', 'obj': None},
- libvect.GV_AREA: {'label': 'area', 'obj': Area},
- libvect.GV_VOLUME: {'label': 'volume', 'obj': None}, }
- GEOOBJ = {"areas": Area,
- "dblinks": None,
- "faces": None,
- "holes": None,
- "boundaries": Boundary,
- "islands": Isle,
- "kernels": None,
- "line_points": None,
- "points": Point,
- "lines": Line,
- "nodes": Node,
- "volumes": None}
- def c_read_next_line(c_mapinfo, c_points, c_cats):
- v_id = c_mapinfo.contents.next_line
- v_id = v_id if v_id != 0 else None
- ftype = libvect.Vect_read_next_line(c_mapinfo, c_points, c_cats)
- if ftype == -2:
- raise StopIteration()
- if ftype == -1:
- raise
- return ftype, v_id, c_points, c_cats
- def read_next_line(c_mapinfo, table=None, writeable=False,
- c_points=None, c_cats=None, is2D=True):
- """Return the next geometry feature of a vector map."""
- # Take care of good memory management
- free_points = False
- if c_points == None:
- free_points = True
- free_cats = False
- if c_cats == None:
- free_cats = True
- c_points = c_points if c_points else ctypes.pointer(libvect.line_pnts())
- c_cats = c_cats if c_cats else ctypes.pointer(libvect.line_cats())
- ftype, v_id, c_points, c_cats = c_read_next_line(c_mapinfo, c_points,
- c_cats)
- return GV_TYPE[ftype]['obj'](v_id=v_id, c_mapinfo=c_mapinfo,
- c_points=c_points, c_cats=c_cats,
- table=table, writeable=writeable, is2D=is2D,
- free_points=free_points, free_cats=free_cats)
- def c_read_line(feature_id, c_mapinfo, c_points, c_cats):
- nmax = libvect.Vect_get_num_lines(c_mapinfo)
- if feature_id < 0: # Handle negative indices
- feature_id += nmax + 1
- if feature_id > nmax:
- raise IndexError('Index out of range')
- if feature_id > 0:
- ftype = libvect.Vect_read_line(c_mapinfo, c_points, c_cats, feature_id)
- return feature_id, ftype, c_points, c_cats
- else:
- raise ValueError('The index must be >0, %r given.' % feature_id)
- def read_line(feature_id, c_mapinfo, table=None, writeable=False,
- c_points=None, c_cats=None, is2D=True):
- """Return a geometry object given the feature id and the c_mapinfo.
- """
- # Take care of good memory management
- free_points = False
- if c_points == None:
- free_points = True
- free_cats = False
- if c_cats == None:
- free_cats = True
- c_points = c_points if c_points else ctypes.pointer(libvect.line_pnts())
- c_cats = c_cats if c_cats else ctypes.pointer(libvect.line_cats())
- feature_id, ftype, c_points, c_cats = c_read_line(feature_id, c_mapinfo,
- c_points, c_cats)
- if GV_TYPE[ftype]['obj'] is not None:
- return GV_TYPE[ftype]['obj'](v_id=feature_id, c_mapinfo=c_mapinfo,
- c_points=c_points, c_cats=c_cats,
- table=table, writeable=writeable, is2D=is2D,
- free_points=free_points,
- free_cats=free_cats)
- if __name__ == "__main__":
- import doctest
- from grass.pygrass import utils
- utils.create_test_vector_map(test_vector_name)
- doctest.testmod()
- """Remove the generated vector map, if exist"""
- from grass.pygrass.utils import get_mapset_vector
- from grass.script.core import run_command
- mset = get_mapset_vector(test_vector_name, mapset='')
- if mset:
- run_command("g.remove", flags='f', type='vector', name=test_vector_name)
|