Commit 16bd1d75 authored by Joseph Siddons's avatar Joseph Siddons
Browse files

refactor(quadtree)!: Use bounding box definition for Rectangle class

parent 724926b6
...@@ -90,59 +90,55 @@ class Rectangle: ...@@ -90,59 +90,55 @@ class Rectangle:
Parameters Parameters
---------- ----------
lon : float west : float
Horizontal centre of the rectangle Western boundary of the Rectangle
lat : float east : float
Vertical centre of the rectangle Eastern boundary of the Rectangle
lon_range : float south : float
Width of the rectangle Southern boundary of the Rectangle
lat_range : float north : float
Height of the rectangle Northern boundary of the Rectangle
""" """
lon: float west: float
lat: float east: float
lon_range: float south: float
lat_range: float north: float
def __post_init__(self): def __post_init__(self):
if self.lon > 180: if self.east > 180 or self.east < -180:
self.lon -= 360 self.east = ((self.east + 540) % 360) - 180
if self.lat > 90 or self.lat < -90: if self.west > 180 or self.west < -180:
self.west = ((self.west + 540) % 360) - 180
if self.north > 90 or self.south < -90:
raise LatitudeError( raise LatitudeError(
f"Central latitude value out of range {self.lat}, " "Latitude bounds are out of bounds. "
+ "should be between -90, 90 degrees" + f"{self.north = }, {self.south = }"
) )
@property @property
def west(self) -> float: def lat_range(self) -> float:
"""Western boundary of the Rectangle""" """Latitude range of the Rectangle"""
return (((self.lon - self.lon_range / 2) + 540) % 360) - 180 return self.north - self.south
@property @property
def east(self) -> float: def lat(self) -> float:
"""Eastern boundary of the Rectangle""" """Centre latitude of the Rectangle"""
return (((self.lon + self.lon_range / 2) + 540) % 360) - 180 return self.south + self.lat_range / 2
@property @property
def north(self) -> float: def lon_range(self) -> float:
"""Northern boundary of the Rectangle""" """Longitude range of the Rectangle"""
north = self.lat + self.lat_range / 2 if self.east < self.west:
if north > 90: return self.east - self.west + 360
raise LatitudeError(
"Rectangle crosses north pole - Use two Rectangles" return self.east - self.west
)
return north
@property @property
def south(self) -> float: def lon(self) -> float:
"""Southern boundary of the Rectangle""" """Centre longitude of the Rectangle"""
south = self.lat - self.lat_range / 2 lon = self.west + self.lon_range / 2
if south < -90: return ((lon + 540) % 360) - 180
raise LatitudeError(
"Rectangle crosses south pole - Use two Rectangles"
)
return south
@property @property
def edge_dist(self) -> float: def edge_dist(self) -> float:
...@@ -332,10 +328,10 @@ class QuadTree: ...@@ -332,10 +328,10 @@ class QuadTree:
"""Divide the QuadTree""" """Divide the QuadTree"""
self.northwest = QuadTree( self.northwest = QuadTree(
Rectangle( Rectangle(
self.boundary.lon - self.boundary.lon_range / 4, self.boundary.west,
self.boundary.lat + self.boundary.lat_range / 4, self.boundary.lon,
self.boundary.lon_range / 2, self.boundary.lat,
self.boundary.lat_range / 2, self.boundary.north,
), ),
capacity=self.capacity, capacity=self.capacity,
depth=self.depth + 1, depth=self.depth + 1,
...@@ -343,10 +339,10 @@ class QuadTree: ...@@ -343,10 +339,10 @@ class QuadTree:
) )
self.northeast = QuadTree( self.northeast = QuadTree(
Rectangle( Rectangle(
self.boundary.lon + self.boundary.lon_range / 4, self.boundary.lon,
self.boundary.lat + self.boundary.lat_range / 4, self.boundary.east,
self.boundary.lon_range / 2, self.boundary.lat,
self.boundary.lat_range / 2, self.boundary.north,
), ),
capacity=self.capacity, capacity=self.capacity,
depth=self.depth + 1, depth=self.depth + 1,
...@@ -354,10 +350,10 @@ class QuadTree: ...@@ -354,10 +350,10 @@ class QuadTree:
) )
self.southwest = QuadTree( self.southwest = QuadTree(
Rectangle( Rectangle(
self.boundary.lon - self.boundary.lon_range / 4, self.boundary.west,
self.boundary.lat - self.boundary.lat_range / 4, self.boundary.lon,
self.boundary.lon_range / 2, self.boundary.south,
self.boundary.lat_range / 2, self.boundary.lat,
), ),
capacity=self.capacity, capacity=self.capacity,
depth=self.depth + 1, depth=self.depth + 1,
...@@ -365,10 +361,10 @@ class QuadTree: ...@@ -365,10 +361,10 @@ class QuadTree:
) )
self.southeast = QuadTree( self.southeast = QuadTree(
Rectangle( Rectangle(
self.boundary.lon + self.boundary.lon_range / 4, self.boundary.lon,
self.boundary.lat - self.boundary.lat_range / 4, self.boundary.east,
self.boundary.lon_range / 2, self.boundary.south,
self.boundary.lat_range / 2, self.boundary.lat,
), ),
capacity=self.capacity, capacity=self.capacity,
depth=self.depth + 1, depth=self.depth + 1,
......
...@@ -7,7 +7,7 @@ from GeoSpatialTools.quadtree import QuadTree, Record, Rectangle, Ellipse ...@@ -7,7 +7,7 @@ from GeoSpatialTools.quadtree import QuadTree, Record, Rectangle, Ellipse
class TestRect(unittest.TestCase): class TestRect(unittest.TestCase):
def test_contains(self): def test_contains(self):
rect = Rectangle(10, 5, 20, 10) rect = Rectangle(0, 20, 0, 10)
points: list[Record] = [ points: list[Record] = [
Record(10, 5), Record(10, 5),
Record(20, 10), Record(20, 10),
...@@ -20,20 +20,20 @@ class TestRect(unittest.TestCase): ...@@ -20,20 +20,20 @@ class TestRect(unittest.TestCase):
assert res == expected assert res == expected
def test_intersection(self): def test_intersection(self):
rect = Rectangle(10, 5, 20, 10) rect = Rectangle(0, 20, 0, 10)
test_rects: list[Rectangle] = [ test_rects: list[Rectangle] = [
Rectangle(10, 5, 18, 8), Rectangle(1, 19, 1, 9),
Rectangle(25, 5, 9, 12), Rectangle(20.5, 29.5, -1, 11),
Rectangle(15, 8, 12, 7), Rectangle(9, 21, 4.5, 11.5),
] ]
expected = [True, False, True] expected = [True, False, True]
res = list(map(rect.intersects, test_rects)) res = list(map(rect.intersects, test_rects))
assert res == expected assert res == expected
def test_wrap(self): def test_wrap(self):
rect = Rectangle(170, 45, 180, 20) rect = Rectangle(80, -100, 35, 55)
assert rect.east < 0 assert rect.lon == 170
assert rect.west > 0 assert rect.lat == 45
test_points: list[Record] = [ test_points: list[Record] = [
Record(-140, 40), Record(-140, 40),
Record(0, 50), Record(0, 50),
...@@ -43,20 +43,19 @@ class TestRect(unittest.TestCase): ...@@ -43,20 +43,19 @@ class TestRect(unittest.TestCase):
res = list(map(rect.contains, test_points)) res = list(map(rect.contains, test_points))
assert res == expected assert res == expected
test_rect = Rectangle(-100, 40, 80, 40) test_rect = Rectangle(-140, -60, 20, 60)
assert test_rect.east < rect.west
assert rect.intersects(test_rect) assert rect.intersects(test_rect)
class TestQuadTree(unittest.TestCase): class TestQuadTree(unittest.TestCase):
def test_divides(self): def test_divides(self):
boundary = Rectangle(10, 4, 20, 8) boundary = Rectangle(0, 20, 0, 8)
qtree = QuadTree(boundary) qtree = QuadTree(boundary)
expected: list[Rectangle] = [ expected: list[Rectangle] = [
Rectangle(5, 6, 10, 4), Rectangle(0, 10, 4, 8),
Rectangle(15, 6, 10, 4), Rectangle(10, 20, 4, 8),
Rectangle(5, 2, 10, 4), Rectangle(0, 10, 0, 4),
Rectangle(15, 2, 10, 4), Rectangle(10, 20, 0, 4),
] ]
qtree.divide() qtree.divide()
res = [ res = [
...@@ -68,7 +67,7 @@ class TestQuadTree(unittest.TestCase): ...@@ -68,7 +67,7 @@ class TestQuadTree(unittest.TestCase):
assert res == expected assert res == expected
def test_insert(self): def test_insert(self):
boundary = Rectangle(10, 4, 20, 8) boundary = Rectangle(0, 20, 0, 8)
qtree = QuadTree(boundary, capacity=3) qtree = QuadTree(boundary, capacity=3)
points: list[Record] = [ points: list[Record] = [
Record(10, 5), Record(10, 5),
...@@ -97,7 +96,7 @@ class TestQuadTree(unittest.TestCase): ...@@ -97,7 +96,7 @@ class TestQuadTree(unittest.TestCase):
assert res == expected assert res == expected
def test_query(self): def test_query(self):
boundary = Rectangle(10, 4, 20, 8) boundary = Rectangle(0, 20, 0, 8)
qtree = QuadTree(boundary, capacity=3) qtree = QuadTree(boundary, capacity=3)
points: list[Record] = [ points: list[Record] = [
Record(10, 5), Record(10, 5),
...@@ -106,7 +105,7 @@ class TestQuadTree(unittest.TestCase): ...@@ -106,7 +105,7 @@ class TestQuadTree(unittest.TestCase):
Record(-2, -9.2), Record(-2, -9.2),
Record(12.8, 2.1), Record(12.8, 2.1),
] ]
test_rect = Rectangle(12.5, 2.5, 1, 1) test_rect = Rectangle(12, 13, 2, 3)
expected = [Record(12.8, 2.1)] expected = [Record(12.8, 2.1)]
for point in points: for point in points:
...@@ -118,10 +117,20 @@ class TestQuadTree(unittest.TestCase): ...@@ -118,10 +117,20 @@ class TestQuadTree(unittest.TestCase):
def test_wrap_query(self): def test_wrap_query(self):
N = 100 N = 100
qt_boundary = Rectangle(0, 0, 360, 180) qt_boundary = Rectangle(-180, 180, -90, 90)
assert qt_boundary.lon == 0
assert qt_boundary.lon_range == 360
assert qt_boundary.lat == 0
assert qt_boundary.lat_range == 180
qt = QuadTree(qt_boundary, capacity=3) qt = QuadTree(qt_boundary, capacity=3)
quert_rect = Rectangle(170, 45, 60, 10) quert_rect = Rectangle(140, -160, 40, 50)
assert quert_rect.lon == 170
assert quert_rect.lon_range == 60
assert quert_rect.lat == 45
assert quert_rect.lat_range == 10
points_want: list[Record] = [ points_want: list[Record] = [
Record(175, 43), Record(175, 43),
Record(-172, 49), Record(-172, 49),
...@@ -163,7 +172,7 @@ class TestQuadTree(unittest.TestCase): ...@@ -163,7 +172,7 @@ class TestQuadTree(unittest.TestCase):
assert not ellipse.contains(Record(12.5, 3.01)) assert not ellipse.contains(Record(12.5, 3.01))
assert not ellipse.contains(Record(12.5, 1.99)) assert not ellipse.contains(Record(12.5, 1.99))
boundary = Rectangle(10, 4, 20, 8) boundary = Rectangle(0, 20, 0, 8)
qtree = QuadTree(boundary, capacity=3) qtree = QuadTree(boundary, capacity=3)
points: list[Record] = [ points: list[Record] = [
Record(10, 5), Record(10, 5),
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment