diff --git a/ci/311_shapely_dev.yaml b/ci/311_shapely_dev.yaml new file mode 100644 index 000000000..8b68a0d6d --- /dev/null +++ b/ci/311_shapely_dev.yaml @@ -0,0 +1,31 @@ +name: test +channels: + - conda-forge +dependencies: + - python=3.11 + - platformdirs + - beautifulsoup4 + - jinja2 + - pandas>=1.0 + - scipy>=1.0 + - xarray + # testing + - codecov + - matplotlib + - pytest + - pytest-cov + - pytest-xdist + # optional + - geopandas>=0.12.0 + - joblib + - networkx + - packaging + - shapely>=2.0b1 + - xarray + - zstd + # for docs build action (this env only) + - nbsphinx + - numpydoc + - sphinx + - sphinxcontrib-bibtex + - sphinx_bootstrap_theme diff --git a/libpysal/cg/rtree.py b/libpysal/cg/rtree.py index a1bcbf8ec..2b3dc6cf4 100644 --- a/libpysal/cg/rtree.py +++ b/libpysal/cg/rtree.py @@ -59,17 +59,17 @@ def coords(self) -> tuple: def overlap(self, orect): """Return the overlapping area of two rectangles. - + Parameters ---------- orect : libpysal.cg.Rect Another rectangle. - + Returns ------- overlapping_area : float The area of the overlap between ``orect`` and ``self``. - + """ overlapping_area = self.intersect(orect).area() @@ -108,7 +108,7 @@ def extent(self) -> tuple: def grow(self, amt=None, sf=0.5): """Grow the bounds of a rectangle. - + Parameters ---------- amt : float @@ -116,12 +116,12 @@ def grow(self, amt=None, sf=0.5): triggers the value of ``BUFFER``. sf : float The scale factor for ``amt``. Default is ``0.5``. - + Returns ------- rect : libpysal.cg.Rect A new rectangle grown by ``amt`` and scaled by ``sf``. - + """ if not amt: @@ -133,12 +133,12 @@ def grow(self, amt=None, sf=0.5): def intersect(self, o): """Find the intersection of two rectangles. - + Parameters ---------- o : libpysal.cg.Rect Another rectangle. - + Returns ------- intersection : {libpysal.cg.NullRect, libpysal.cg.Rect} @@ -168,17 +168,17 @@ def intersect(self, o): def does_contain(self, o): """Check whether the rectangle contains the other rectangle. - + Parameters ---------- o : libpysal.cg.Rect Another rectangle. - + Returns ------- dc : bool ``True`` if ``self`` contains ``o`` otherwise ``False``. - + """ dc = self.does_containpoint((o.x, o.y)) and self.does_containpoint((o.xx, o.yy)) @@ -187,17 +187,17 @@ def does_contain(self, o): def does_intersect(self, o): """Check whether the rectangles interect. - + Parameters ---------- o : libpysal.cg.Rect Another rectangle. - + Returns ------- dcp : bool ``True`` if ``self`` intersects ``o`` otherwise ``False``. - + """ di = self.intersect(o).area() > 0 @@ -206,17 +206,17 @@ def does_intersect(self, o): def does_containpoint(self, p): """Check whether the rectangle contains a point or not. - + Parameters ---------- p : libpysal.cg.Point A point. - + Returns ------- dcp : bool ``True`` if ``self`` contains ``p`` otherwise ``False``. - + """ x, y = p @@ -227,12 +227,12 @@ def does_containpoint(self, p): def union(self, o): """Union two rectangles. - + Parameters ---------- o : libpysal.cg.Rect Another rectangle. - + Returns ------- res : libpysal.cg.Rect @@ -265,12 +265,12 @@ def union(self, o): def union_point(self, o): """Union the rectangle and a point - + Parameters ---------- o : libpysal.cg.Point A point. - + Returns ------- res : libpysal.cg.Rect @@ -309,17 +309,17 @@ def diagonal(self) -> float: def union_all(kids): """Create union of all child rectangles. - + Parameters ---------- kids : list A list of ``libpysal.cg._NodeCursor`` objects. - + Returns ------- cur : {libpysal.cg.Rect, libpysal.cg.NullRect} The unioned result of all child rectangles. - + """ cur = NullRect @@ -337,7 +337,7 @@ def Rtree(): class RTree(object): """An RTree for efficiently querying space based on intersecting rectangles. - + Attributes ---------- count : int @@ -357,12 +357,12 @@ class RTree(object): The pool of leaf objects in the tree. cursor : libpysal.cg._NodeCursor The non-root node and all its children. - + Examples -------- - + Instantiate an ``RTree``. - + >>> from libpysal.cg import RTree, Chain >>> segments = [ ... [(0.0, 1.5), (1.5, 1.5)], @@ -374,10 +374,10 @@ class RTree(object): >>> rt = RTree() >>> for segment in segments: ... rt.insert(segment, Rect(*segment.bounding_box).grow(sf=10.)) - + Examine the tree generation statistics. The statistics here are all 0 due to the simple structure of the tree in this example. - + >>> rt.stats {'overflow_f': 0, 'avg_overflow_t_f': 0.0, @@ -386,51 +386,51 @@ class RTree(object): 'sum_kmeans_iter_f': 0, 'count_kmeans_iter_f': 0, 'avg_kmeans_iter_f': 0.0} - + Examine the number of nodes and leaves. There five nodes and four leaves (the root plus its four children). - + >>> rt.count, rt.leaf_count (5, 4) - + The pool of nodes are the node IDs in the tree. - + >>> rt.node_pool array('L', [0, 4, 0, 0, 1, 1, 2, 2, 3, 3]) - + The pool of leaves are the geometric objects that were inserted into the tree. - + >>> rt.leaf_pool[0].vertices [(0.0, 1.5), (1.5, 1.5)] - + The pool of rectangles are the bounds of partitioned space in the tree. Examine the first one. - + >>> rt.rect_pool[:4] array('d', [-2.220446049250313e-15, -2.220446049250313e-15, 3.000000000000002, 3.000000000000002]) - + Add the bounding box of a leaf to the tree manually. - + >>> rt.add(Chain(((2,2), (4,4))), (2,2,4,4)) >>> rt.count, rt.leaf_count (6, 5) - + Query the tree for an intersection. One object is contained in this query. - + >>> rt.intersection([.4, 2.1, .9, 2.6])[0].vertices [(0.5, 2), (1, 2.5)] - + Query the tree with a much larger box. All objects are contained in this query. - + >>> len(rt.intersection([-1, -1, 4, 4])) == rt.leaf_count True - + Query the tree with box outside the tree objects. No objects are contained in this query. - + >>> rt.intersection([5, 5, 6, 6]) [] - + """ def __init__(self): @@ -471,14 +471,14 @@ def _ensure_pool(self, idx: int): def insert(self, o, orect): """Insert an object and its bounding box into the tree. - + Parameters ---------- o : libpysal.cg.{Point, Chain, Rectangle, Polygon} The object to insert into the tree. orect : ibpysal.cg.Rect The object's bounding box. - + """ self.cursor.insert(o, orect) @@ -486,13 +486,13 @@ def insert(self, o, orect): def query_rect(self, r): """Query a rectangle. - + Parameters ---------- r : {tuple, libpysal.cg.Point} The bounding box of the rectangle in question; a :math:`(minx,miny,maxx,maxy)` set of coordinates. - + Yields ------ x : generator @@ -504,12 +504,12 @@ def query_rect(self, r): def query_point(self, p): """Query a point. - + Parameters ---------- p : {tuple, libpysal.cg.Point} The point in question; an :math:`(x,y)` coordinate. - + Yields ------ x : generator @@ -527,12 +527,12 @@ def walk(self, pred): def intersection(self, boundingbox): """Query for an intersection between leaves in the ``RTree`` and the bounding box of an object. - + Parameters ---------- boundingbox : list The bounding box: ``[minx, miny, maxx, maxy]``. - + Returns ------- objs : list @@ -557,7 +557,7 @@ def add(self, id, boundingbox): An object id. boundingbox : list The bounding box: ``[minx, miny, maxx, maxy]``. - + """ self.cursor.insert(id, Rect(*boundingbox)) @@ -566,7 +566,7 @@ def add(self, id, boundingbox): class _NodeCursor(object): """An internal class for keeping track of, and reorganizing, the structure and composition of the ``RTree``. - + Parameters ---------- rooto : libpysal.cg.{Point, Chain, Rectangle, Polygon} @@ -579,7 +579,7 @@ class _NodeCursor(object): The ID of the first child of the node. next_sibling : int The ID of the sibling of the node. - + Attributes ---------- root : libpysal.cg.RTree @@ -588,13 +588,13 @@ class _NodeCursor(object): See ``RTree.node_pool``. rpool : array.array See ``RTree.rect_pool``. - + """ @classmethod def create(cls, rooto, rect): """Create a node in the tree structure. - + Parameters ---------- rooto : libpysal.cg.{Point, Chain, Rectangle, Polygon} @@ -603,12 +603,12 @@ def create(cls, rooto, rect): The ID of the node. rect : libpysal.cg.Rect The bounding rectangle of the leaf object. - + Returns ------- retv : libpysal.cg._NodeCursor The generated node. - + """ idx = rooto.count @@ -625,19 +625,19 @@ def create(cls, rooto, rect): @classmethod def create_with_children(cls, children, rooto): """Create a non-leaf node in the tree structure. - + Parameters ---------- children : list The child nodes of the node to be generated rooto : libpysal.cg.{Point, Chain, Rectangle, Polygon} The object from which the node will be generated. - + Returns ------- nc : libpysal.cg._NodeCursor The generated node with children. - + """ rect = union_all([c for c in children]) nr = Rect(rect.x, rect.y, rect.xx, rect.yy) @@ -652,7 +652,7 @@ def create_with_children(cls, children, rooto): @classmethod def create_leaf(cls, rooto, leaf_obj, leaf_rect): """Create a leaf node in the tree structure. - + Parameters ---------- rooto : libpysal.cg.{Point, Chain, Rectangle, Polygon} @@ -661,12 +661,12 @@ def create_leaf(cls, rooto, leaf_obj, leaf_rect): The leaf object. leaf_rect : libpysal.cg.Rect The bounding rectangle of the leaf object. - + Returns ------- res : libpysal.cg._NodeCursor The generated leaf node. - + """ rect = Rect(leaf_rect.x, leaf_rect.y, leaf_rect.xx, leaf_rect.yy) @@ -758,14 +758,14 @@ def p(o, x): def lift(self): """Promote a node to (potentially) rearrange the tree structure for optimal clustering. - + Called from ``_NodeCursor._balance()``. - + Returns ------- lifted : libpysal.cg._NodeCursor The lifted node. - + """ lifted = _NodeCursor( @@ -814,12 +814,12 @@ def holds_leaves(self) -> bool: def get_first_child(self): """Get the first child of a node. - + Returns ------- c : libpysal.cg._NodeCursor The first child of the specified node. - + """ fc = self.first_child @@ -867,7 +867,7 @@ def nchildren(self) -> int: def insert(self, leafo, leafrect): """Insert a leaf object into the tree. See ``RTree.insert(o, orect)`` for parameter description. - + """ index = self.index @@ -921,7 +921,7 @@ def _balance(self): and ``silhouette_coeff()`` for (heuristically) optimal clusterings of nodes in the tree structure after the child count of a node has grown past the maximum allowed number (see ``MAXCHILDREN``). - + Called from ``_NodeCursor.insert()``. """ @@ -937,7 +937,10 @@ def _balance(self): clusterings = [ k_means_cluster(self.root, k, s_children) for k in range(2, MAX_KMEANS) ] - score, bestcluster = max([(silhouette_coeff(c), c) for c in clusterings]) + + score, bestcluster = max( + [(silhouette_coeff(c), c) for c in clusterings], key=lambda x: x[0] + ) # generate the (heuristically) optimally-balanced cluster of nodes nodes = [ @@ -959,10 +962,10 @@ def _balance(self): def _set_children(self, cs: list): """Set up the (new/altered) leaf tree structure. - + Called from ``_NodeCursor.create_with_children()`` and ``_NodeCursor._balance()``. - + """ self.first_child = 0 @@ -983,14 +986,14 @@ def _set_children(self, cs: list): self._save_back() def _insert_child(self, c): - """Internal function for child node insertion. + """Internal function for child node insertion. Called from ``_NodeCursor.insert()``. - + Parameters ---------- c : libpysal.cg._NodeCursor A child ``libpysal.cg._NodeCursor`` object. - + """ c.next_sibling = self.first_child @@ -1027,19 +1030,19 @@ def children(self): def avg_diagonals(node, onodes): """Calculate the mean diagonals. - + Parameters ---------- node : libpysal.cg._NodeCursor The target node in question. onodes : ist A list of ``libpysal.cg._NodeCursor`` objects. - + Returns ------- diag_avg : float The mean diagonal distance of ``node`` and ``onodes``. - + """ nidx = node.index @@ -1068,7 +1071,7 @@ def avg_diagonals(node, onodes): def silhouette_w(node, cluster, next_closest_cluster): """Calculate a silhouette score between a certain node and 2 clusters: - + Parameters ---------- node : libpysal.cg._NodeCursor @@ -1077,13 +1080,13 @@ def silhouette_w(node, cluster, next_closest_cluster): A list of ``libpysal.cg._NodeCursor`` objects. next_closest_cluster : list Another list of ``libpysal.cg._NodeCursor`` objects. - + Returns ------- silw : float The silhouette score between ``{node, cluster}`` and ``{node, next_closest_cluster}``. - + """ ndist = avg_diagonals(node, cluster) @@ -1099,17 +1102,17 @@ def silhouette_coeff(clustering): the clusters are well defined, a score of ``0`` indicates the clusters are undefined, and a score of ``-1`` indicates the clusters are defined incorrectly. - + Parameters ---------- clustering : list A list of ``libpysal.cg._NodeCursor`` objects. - + Returns ------- silcoeff : float Score for how well defined the clusters are. - + """ # special case for a clustering of 1.0 @@ -1135,17 +1138,17 @@ def silhouette_coeff(clustering): def center_of_gravity(nodes): """Find the center of gravity of multiple nodes. - + Parameters ---------- nodes : list A list of ``libpysal.cg.RTree`` and ``libpysal.cg._NodeCursor`` objects. - + Returns ------- cog : float The center of gravity of multiple nodes. - + """ totarea = 0.0 @@ -1165,14 +1168,14 @@ def center_of_gravity(nodes): def closest(centroids, node): """Find the closest controid to the node's center of gravity. - + Parameters ---------- centroids : list A list of (x, y) coordinates for the center of other clusters. node : libpysal.cg_NodeCursor A ``libpysal.cg._NodeCursor`` instance. - + Returns ------- ridx : int @@ -1195,7 +1198,7 @@ def closest(centroids, node): def k_means_cluster(root, k, nodes): """Find ``k`` clusters. - + Parameters ---------- root : libpysal.cg.RTree @@ -1204,12 +1207,12 @@ def k_means_cluster(root, k, nodes): The number clusters to find. nodes : list A list of ``libpysal.cg.RTree`` and ``libpysal.cg._NodeCursor`` objects. - + Returns ------- clusters : list Updated versions of ``nodes`` defining new clusters. - + """ t = time.process_time()