Add type information to Quadtree
And some left over typing fixes. Use latest version of Mypy.
This commit is contained in:
parent
6d530df8ef
commit
e0d742ba86
@ -5,7 +5,7 @@ repos:
|
||||
- id: black
|
||||
language_version: python3
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v0.782
|
||||
rev: v0.790
|
||||
hooks:
|
||||
- id: mypy
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
|
@ -1,4 +1,5 @@
|
||||
"""Basic connectors such as Ports and Handles."""
|
||||
from typing import Tuple, Union
|
||||
|
||||
from gaphas.constraint import LineConstraint, PositionConstraint
|
||||
from gaphas.geometry import distance_line_point, distance_point_point
|
||||
@ -28,13 +29,11 @@ class Handle:
|
||||
self._movable = movable
|
||||
self._visible = True
|
||||
|
||||
def _set_pos(self, pos: Position):
|
||||
def _set_pos(self, pos: Union[Position, Tuple[float, float]]):
|
||||
"""
|
||||
Shortcut for ``handle.pos.pos = pos``
|
||||
|
||||
>>> h = Handle((10, 10))
|
||||
>>> h.pos
|
||||
<Position object on (10, 10)>
|
||||
>>> h.pos = (20, 15)
|
||||
>>> h.pos
|
||||
<Position object on (20, 15)>
|
||||
|
@ -64,11 +64,12 @@ class PortoBox(Box):
|
||||
|
||||
# handle for movable port
|
||||
self._hm = Handle(strength=WEAK)
|
||||
self._hm.pos = width, height / 2.0
|
||||
self._hm.pos.x = width
|
||||
self._hm.pos.y = height / 2.0
|
||||
self._handles.append(self._hm)
|
||||
|
||||
# movable port
|
||||
self._ports.append(PointPort(self._hm.pos)) # type: ignore[arg-type]
|
||||
self._ports.append(PointPort(self._hm.pos))
|
||||
|
||||
# keep movable port at right edge
|
||||
self.constraint(vertical=(self._hm.pos, ne.pos), delta=10)
|
||||
|
@ -6,6 +6,7 @@ intersections).
|
||||
A point is represented as a tuple `(x, y)`.
|
||||
"""
|
||||
from math import sqrt
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
class Rectangle:
|
||||
@ -80,6 +81,10 @@ class Rectangle:
|
||||
self.width += delta * 2
|
||||
self.height += delta * 2
|
||||
|
||||
def tuple(self) -> Tuple[float, float, float, float]:
|
||||
"""A type safe version of `tuple(rectangle)`."""
|
||||
return (self.x, self.y, self.width, self.height)
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
>>> Rectangle(5,7,20,25)
|
||||
|
@ -90,15 +90,15 @@ class Matrix:
|
||||
def to_cairo(self):
|
||||
return self._matrix
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Matrix):
|
||||
return self._matrix == other._matrix # type: ignore[no-any-return]
|
||||
return self._matrix == other._matrix
|
||||
else:
|
||||
return False
|
||||
|
||||
def __ne__(self, other) -> bool:
|
||||
def __ne__(self, other):
|
||||
if isinstance(other, Matrix):
|
||||
return self._matrix != other._matrix # type: ignore[no-any-return]
|
||||
return self._matrix != other._matrix
|
||||
else:
|
||||
return False
|
||||
|
||||
|
@ -17,12 +17,20 @@ as a Q-tree. All forms of Quadtrees share some common features:
|
||||
|
||||
(From Wikipedia, the free encyclopedia)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import operator
|
||||
from typing import Callable, Dict, Generic, List, Optional, Tuple, TypeVar
|
||||
|
||||
from gaphas.geometry import rectangle_clip, rectangle_contains, rectangle_intersects
|
||||
|
||||
Bounds = Tuple[float, float, float, float]
|
||||
|
||||
class Quadtree:
|
||||
T = TypeVar("T")
|
||||
D = TypeVar("D")
|
||||
|
||||
|
||||
class Quadtree(Generic[T, D]):
|
||||
"""The Quad-tree.
|
||||
|
||||
Rectangles use the same scheme throughout Gaphas: (x, y, width, height).
|
||||
@ -78,7 +86,7 @@ class Quadtree:
|
||||
>>> qtree.rebuild()
|
||||
"""
|
||||
|
||||
def __init__(self, bounds=(0, 0, 0, 0), capacity=10):
|
||||
def __init__(self, bounds: Bounds = (0, 0, 0, 0), capacity=10):
|
||||
"""Create a new Quadtree instance.
|
||||
|
||||
Bounds is the boundaries of the quadtree. this is fixed and do not
|
||||
@ -87,14 +95,14 @@ class Quadtree:
|
||||
Capacity defines the number of elements in one tree bucket (default: 10)
|
||||
"""
|
||||
self._capacity = capacity
|
||||
self._bucket = QuadtreeBucket(bounds, capacity)
|
||||
self._bucket: QuadtreeBucket[T] = QuadtreeBucket(bounds, capacity)
|
||||
|
||||
# Easy lookup item->(bounds, data, clipped bounds) mapping
|
||||
self._ids = {}
|
||||
self._ids: Dict[T, Tuple[Bounds, Optional[D], Bounds]] = {}
|
||||
|
||||
bounds = property(lambda s: s._bucket.bounds)
|
||||
|
||||
def resize(self, bounds):
|
||||
def resize(self, bounds: Bounds) -> None:
|
||||
"""Resize the tree.
|
||||
|
||||
The tree structure is rebuild.
|
||||
@ -102,7 +110,8 @@ class Quadtree:
|
||||
self._bucket = QuadtreeBucket(bounds, self._capacity)
|
||||
self.rebuild()
|
||||
|
||||
def get_soft_bounds(self):
|
||||
@property
|
||||
def soft_bounds(self) -> Bounds:
|
||||
"""Calculate the size of all items in the tree. This size may be beyond
|
||||
the limits of the tree itself.
|
||||
|
||||
@ -121,29 +130,17 @@ class Quadtree:
|
||||
>>> qtree.bounds
|
||||
(0, 0, 0, 0)
|
||||
"""
|
||||
x_y_w_h = list(
|
||||
zip( # type: ignore[call-overload]
|
||||
*list(
|
||||
map(
|
||||
operator.getitem,
|
||||
iter(list(self._ids.values())),
|
||||
[0] * len(self._ids),
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
x_y_w_h = list(zip(*[d[0] for d in self._ids.values()]))
|
||||
if not x_y_w_h:
|
||||
return 0, 0, 0, 0
|
||||
x0 = min(x_y_w_h[0])
|
||||
y0 = min(x_y_w_h[1])
|
||||
add = operator.add
|
||||
x1 = max(list(map(add, x_y_w_h[0], x_y_w_h[2])))
|
||||
y1 = max(list(map(add, x_y_w_h[1], x_y_w_h[3])))
|
||||
return (x0, y0, x1 - x0, y1 - y0)
|
||||
x1 = max(map(add, x_y_w_h[0], x_y_w_h[2]))
|
||||
y1 = max(map(add, x_y_w_h[1], x_y_w_h[3]))
|
||||
return x0, y0, x1 - x0, y1 - y0
|
||||
|
||||
soft_bounds = property(get_soft_bounds)
|
||||
|
||||
def add(self, item, bounds, data=None):
|
||||
def add(self, item: T, bounds: Bounds, data: D = None):
|
||||
"""Add an item to the tree.
|
||||
|
||||
If an item already exists, its bounds are updated and the item
|
||||
@ -176,7 +173,7 @@ class Quadtree:
|
||||
self._bucket.find_bucket(clipped_bounds).add(item, clipped_bounds)
|
||||
self._ids[item] = (bounds, data, clipped_bounds)
|
||||
|
||||
def remove(self, item):
|
||||
def remove(self, item: T):
|
||||
"""Remove an item from the tree."""
|
||||
bounds, data, clipped_bounds = self._ids[item]
|
||||
del self._ids[item]
|
||||
@ -199,15 +196,15 @@ class Quadtree:
|
||||
self._bucket.find_bucket(clipped_bounds).add(item, clipped_bounds)
|
||||
self._ids[item] = (bounds, data, clipped_bounds)
|
||||
|
||||
def get_bounds(self, item):
|
||||
def get_bounds(self, item: T):
|
||||
"""Return the bounding box for the given item."""
|
||||
return self._ids[item][0]
|
||||
|
||||
def get_data(self, item):
|
||||
def get_data(self, item: T):
|
||||
"""Return the data for the given item, None if no data was provided."""
|
||||
return self._ids[item][1]
|
||||
|
||||
def get_clipped_bounds(self, item):
|
||||
def get_clipped_bounds(self, item: T):
|
||||
"""Return the bounding box for the given item.
|
||||
|
||||
The bounding box is clipped on the boundaries of the tree
|
||||
@ -215,14 +212,14 @@ class Quadtree:
|
||||
"""
|
||||
return self._ids[item][2]
|
||||
|
||||
def find_inside(self, rect):
|
||||
def find_inside(self, rect: Bounds):
|
||||
"""Find all items in the given rectangle (x, y, with, height).
|
||||
|
||||
Returns a set.
|
||||
"""
|
||||
return set(self._bucket.find(rect, method=rectangle_contains))
|
||||
|
||||
def find_intersect(self, rect):
|
||||
def find_intersect(self, rect: Bounds):
|
||||
"""Find all items that intersect with the given rectangle (x, y, width,
|
||||
height).
|
||||
|
||||
@ -234,27 +231,27 @@ class Quadtree:
|
||||
"""Return number of items in tree."""
|
||||
return len(self._ids)
|
||||
|
||||
def __contains__(self, item):
|
||||
def __contains__(self, item: T):
|
||||
"""Check if an item is in tree."""
|
||||
return item in self._ids
|
||||
|
||||
def dump(self):
|
||||
def dump(self) -> None:
|
||||
"""Print structure to stdout."""
|
||||
self._bucket.dump()
|
||||
|
||||
|
||||
class QuadtreeBucket:
|
||||
class QuadtreeBucket(Generic[T]):
|
||||
"""A node in a Quadtree structure."""
|
||||
|
||||
def __init__(self, bounds, capacity):
|
||||
def __init__(self, bounds: Bounds, capacity: int):
|
||||
"""Set bounding box for the node as (x, y, width, height)."""
|
||||
self.bounds = bounds
|
||||
self.capacity = capacity
|
||||
|
||||
self.items = {}
|
||||
self._buckets = []
|
||||
self.items: Dict[T, Bounds] = {}
|
||||
self._buckets: List[QuadtreeBucket[T]] = []
|
||||
|
||||
def add(self, item, bounds):
|
||||
def add(self, item: T, bounds: Bounds):
|
||||
"""Add an item to the quadtree.
|
||||
|
||||
The bucket is split when necessary. Items are otherwise added to
|
||||
@ -281,7 +278,7 @@ class QuadtreeBucket:
|
||||
else:
|
||||
self.items[item] = bounds
|
||||
|
||||
def remove(self, item):
|
||||
def remove(self, item: T) -> None:
|
||||
"""Remove an item from the quadtree bucket.
|
||||
|
||||
The item should be contained by *this* bucket (not a sub-
|
||||
@ -289,7 +286,7 @@ class QuadtreeBucket:
|
||||
"""
|
||||
del self.items[item]
|
||||
|
||||
def update(self, item, new_bounds):
|
||||
def update(self, item: T, new_bounds: Bounds) -> None:
|
||||
"""Update the position of an item within the current bucket.
|
||||
|
||||
The item should live in the current bucket, but may be placed in
|
||||
@ -299,7 +296,7 @@ class QuadtreeBucket:
|
||||
self.remove(item)
|
||||
self.find_bucket(new_bounds).add(item, new_bounds)
|
||||
|
||||
def find_bucket(self, bounds):
|
||||
def find_bucket(self, bounds: Bounds):
|
||||
"""Find the bucket that holds a bounding box.
|
||||
|
||||
This method should be used to find a bucket that fits, before
|
||||
@ -322,7 +319,7 @@ class QuadtreeBucket:
|
||||
return self
|
||||
return self._buckets[index].find_bucket(bounds)
|
||||
|
||||
def find(self, rect, method):
|
||||
def find(self, rect: Bounds, method: Callable[[Bounds, Bounds], bool]):
|
||||
"""Find all items in the given rectangle (x, y, with, height). Method
|
||||
can be either the contains or intersects function.
|
||||
|
||||
|
@ -1,10 +1,13 @@
|
||||
"""This module contains everything to display a Canvas on a screen."""
|
||||
from typing import Tuple
|
||||
|
||||
import cairo
|
||||
from gi.repository import Gdk, GLib, GObject, Gtk
|
||||
|
||||
from gaphas.canvas import Context, instant_cairo_context
|
||||
from gaphas.decorators import AsyncIO
|
||||
from gaphas.geometry import Rectangle, distance_point_point_fast
|
||||
from gaphas.item import Item
|
||||
from gaphas.matrix import Matrix
|
||||
from gaphas.painter import BoundingBoxPainter, DefaultPainter, ItemPainter
|
||||
from gaphas.quadtree import Quadtree
|
||||
@ -33,7 +36,7 @@ class View:
|
||||
self._hovered_item = None
|
||||
self._dropzone_item = None
|
||||
|
||||
self._qtree = Quadtree()
|
||||
self._qtree: Quadtree[Item, Tuple[float, float, float, float]] = Quadtree()
|
||||
self._bounds = Rectangle(0, 0, 0, 0)
|
||||
|
||||
self._canvas = None
|
||||
@ -667,7 +670,7 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable, View):
|
||||
x0, y0 = i2v(bounds[0], bounds[1])
|
||||
x1, y1 = i2v(bounds[2], bounds[3])
|
||||
vbounds = Rectangle(x0, y0, x1=x1, y1=y1)
|
||||
self._qtree.add(i, vbounds, bounds)
|
||||
self._qtree.add(i, vbounds.tuple(), bounds)
|
||||
|
||||
self.update_bounding_box(set(dirty_items))
|
||||
|
||||
|
@ -1,15 +1,14 @@
|
||||
import pytest
|
||||
|
||||
from gaphas.geometry import Rectangle
|
||||
from gaphas.quadtree import Quadtree
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def qtree():
|
||||
qtree = Quadtree((0, 0, 100, 100))
|
||||
qtree: Quadtree[str, None] = Quadtree((0, 0, 100, 100))
|
||||
for i in range(0, 100, 10):
|
||||
for j in range(0, 100, 10):
|
||||
qtree.add(item=f"{i:d}x{j:d}", bounds=Rectangle(i, j, 10, 10))
|
||||
qtree.add(item=f"{i:d}x{j:d}", bounds=(i, j, 10, 10))
|
||||
return qtree
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user