123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884 |
- # -*- 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)
- 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)
|