Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Open sidebar
Joseph Siddons
GeoSpatialTools
Commits
762dfafa
Commit
762dfafa
authored
5 months ago
by
Joseph Siddons
Browse files
Options
Download
Email Patches
Plain Diff
refactor(Rectangle)!: Fix wrapping at -180, 180. Rewrite as dataclass.
Adds north, east, south, west attributes
parent
a10a6cf1
main
10-improve-documentation
v0.11.2
v0.11.1
v0.11.0
v0.10.1
v0.10.0
v0.9.0
v0.8.0
v0.7.1
v0.7.0
v0.6.0
v0.5.0
No related merge requests found
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
224 additions
and
195 deletions
+224
-195
GeoSpatialTools/octtree.py
GeoSpatialTools/octtree.py
+135
-119
GeoSpatialTools/quadtree.py
GeoSpatialTools/quadtree.py
+89
-76
No files found.
GeoSpatialTools/octtree.py
View file @
762dfafa
from
dat
etime
import
dat
etime
,
timedelta
from
dat
aclasses
import
dat
aclass
import
datetime
import
datetime
from
.distance_metrics
import
haversine
,
destination
from
.distance_metrics
import
haversine
,
destination
from
.utils
import
LatitudeError
from
.utils
import
LatitudeError
...
@@ -83,6 +83,7 @@ class SpaceTimeRecords(list[SpaceTimeRecord]):
...
@@ -83,6 +83,7 @@ class SpaceTimeRecords(list[SpaceTimeRecord]):
"""List of SpaceTimeRecords"""
"""List of SpaceTimeRecords"""
@
dataclass
class
SpaceTimeRectangle
:
class
SpaceTimeRectangle
:
"""
"""
A simple Space Time SpaceTimeRectangle class.
A simple Space Time SpaceTimeRectangle class.
...
@@ -104,73 +105,134 @@ class SpaceTimeRectangle:
...
@@ -104,73 +105,134 @@ class SpaceTimeRectangle:
Horizontal centre of the rectangle (longitude).
Horizontal centre of the rectangle (longitude).
lat : float
lat : float
Vertical centre of the rectangle (latitude).
Vertical centre of the rectangle (latitude).
datetime : datetime
datetime : datetime
.datetime
Datetime centre of the rectangle.
Datetime centre of the rectangle.
w : float
w : float
Width of the rectangle (longitude range).
Width of the rectangle (longitude range).
h : float
h : float
Height of the rectangle (latitude range).
Height of the rectangle (latitude range).
dt : timedelta
dt :
datetime.
timedelta
time extent of the rectangle.
time extent of the rectangle.
"""
"""
def
__init__
(
lon
:
float
self
,
lat
:
float
lon
:
float
,
date
:
datetime
.
datetime
l
at
:
float
,
l
on_range
:
float
datetime
:
datetime
,
lat_range
:
float
lon_range
:
float
,
dt
:
datetime
.
timedelta
lat_range
:
float
,
d
t
:
timedelta
,
d
ef
__post_init__
(
self
):
)
->
None
:
if
self
.
lon
>
180
:
self
.
lon
=
lon
self
.
lon
-
=
360
self
.
lat
=
lat
if
self
.
lat
>
90
or
self
.
lat
<
-
90
:
self
.
lon_range
=
lon_range
raise
LatitudeError
(
self
.
lat_range
=
lat_range
f
"Central latitude value out of range
{
self
.
lat
}
, "
self
.
datetime
=
datetime
+
"should be between -90, 90 degrees"
self
.
dt
=
dt
)
def
__str__
(
self
)
->
str
:
@
property
return
f
"SpaceTimeRectangle(x =
{
self
.
lon
}
, y =
{
self
.
lat
}
, w =
{
self
.
lon_range
}
, h =
{
self
.
lat_range
}
, t =
{
self
.
datetime
}
, dt =
{
self
.
dt
}
)"
def
west
(
self
)
->
float
:
"""Western boundary of the Rectangle"""
return
(((
self
.
lon
-
self
.
lon_range
/
2
)
+
540
)
%
360
)
-
180
@
property
def
east
(
self
)
->
float
:
"""Eastern boundary of the Rectangle"""
return
(((
self
.
lon
+
self
.
lon_range
/
2
)
+
540
)
%
360
)
-
180
@
property
def
north
(
self
)
->
float
:
"""Northern boundary of the Rectangle"""
north
=
self
.
lat
+
self
.
lat_range
/
2
if
north
>
90
:
raise
LatitudeError
(
"Rectangle crosses north pole - Use two Rectangles"
)
return
north
def
__eq__
(
self
,
other
:
object
)
->
bool
:
@
property
return
(
def
south
(
self
)
->
float
:
isinstance
(
other
,
SpaceTimeRectangle
)
"""Southern boundary of the Rectangle"""
and
self
.
lon
==
other
.
lon
south
=
self
.
lat
-
self
.
lat_range
/
2
and
self
.
lat
==
other
.
lat
if
south
<
-
90
:
and
self
.
lon_range
==
other
.
lon_range
raise
LatitudeError
(
and
self
.
lat_range
==
other
.
lat_range
"Rectangle crosses south pole - Use two Rectangles"
and
self
.
datetime
==
other
.
datetime
)
and
self
.
dt
==
other
.
dt
return
south
@
property
def
start
(
self
)
->
datetime
.
datetime
:
"""Start date of the Rectangle"""
return
self
.
date
-
self
.
dt
/
2
@
property
def
end
(
self
)
->
datetime
.
datetime
:
"""End date of the Rectangle"""
return
self
.
date
+
self
.
dt
/
2
@
property
def
edge_dist
(
self
)
->
float
:
"""Approximate maximum distance from the centre to an edge"""
corner_dist
=
max
(
haversine
(
self
.
lon
,
self
.
lat
,
self
.
east
,
self
.
north
),
haversine
(
self
.
lon
,
self
.
lat
,
self
.
east
,
self
.
south
),
)
)
if
self
.
east
*
self
.
west
<
0
:
corner_dist
=
max
(
corner_dist
,
haversine
(
self
.
lon
,
self
.
lat
,
self
.
east
,
0
),
)
return
corner_dist
def
_test_east_west
(
self
,
lon
:
float
)
->
bool
:
if
self
.
lon_range
>=
360
:
# Rectangle encircles earth
return
True
if
self
.
east
>
self
.
lon
and
self
.
west
<
self
.
lon
:
return
lon
<=
self
.
east
and
lon
>=
self
.
west
if
self
.
east
<
self
.
lon
:
return
not
(
lon
>
self
.
east
and
lon
<
self
.
west
)
if
self
.
west
>
self
.
lon
:
return
not
(
lon
<
self
.
east
and
lon
>
self
.
west
)
return
False
def
_test_north_south
(
self
,
lat
:
float
)
->
bool
:
return
lat
<=
self
.
north
and
lat
>=
self
.
south
def
contains
(
self
,
point
:
SpaceTimeRecord
)
->
bool
:
def
contains
(
self
,
point
:
SpaceTimeRecord
)
->
bool
:
"""Test if a point is contained within the SpaceTimeRectangle"""
"""Test if a point is contained within the SpaceTimeRectangle"""
return
(
if
point
.
datetime
>
self
.
end
or
point
.
datetime
<
self
.
start
:
point
.
lon
<=
self
.
lon
+
self
.
lon_range
/
2
return
False
and
point
.
lon
>=
self
.
lon
-
self
.
lon_range
/
2
return
self
.
_test_north_south
(
point
.
lat
)
and
self
.
_test_east_west
(
and
point
.
lat
<=
self
.
lat
+
self
.
lat_range
/
2
point
.
lon
and
point
.
lat
>=
self
.
lat
-
self
.
lat_range
/
2
and
point
.
datetime
<=
self
.
datetime
+
self
.
dt
/
2
and
point
.
datetime
>=
self
.
datetime
-
self
.
dt
/
2
)
)
def
intersects
(
self
,
other
:
object
)
->
bool
:
def
intersects
(
self
,
other
:
object
)
->
bool
:
"""Test if another Rectangle object intersects this Rectangle"""
"""Test if another Rectangle object intersects this Rectangle"""
return
isinstance
(
other
,
SpaceTimeRectangle
)
and
not
(
if
not
isinstance
(
other
,
SpaceTimeRectangle
):
self
.
lon
-
self
.
lon_range
/
2
>
other
.
lon
+
other
.
lon_range
/
2
raise
TypeError
(
or
self
.
lon
+
self
.
lon_range
/
2
<
other
.
lon
-
other
.
lon_range
/
2
f
"other must be a Rectangle class, got
{
type
(
other
)
}
"
or
self
.
lat
-
self
.
lat_range
/
2
>
other
.
lat
+
other
.
lat_range
/
2
)
or
self
.
lat
+
self
.
lat_range
/
2
<
other
.
lat
-
other
.
lat_range
/
2
if
other
.
end
<
self
.
start
or
other
.
start
>
self
.
end
:
or
self
.
datetime
-
self
.
dt
/
2
>
other
.
datetime
+
other
.
dt
/
2
# Not in the same time range
or
self
.
datetime
+
self
.
dt
/
2
<
other
.
datetime
-
other
.
dt
/
2
return
False
if
other
.
south
>
self
.
north
:
# Other is fully north of self
return
False
if
other
.
north
<
self
.
south
:
# Other is fully south of self
return
False
# Handle east / west edges
return
self
.
_test_east_west
(
other
.
west
)
or
self
.
_test_east_west
(
other
.
east
)
)
def
nearby
(
def
nearby
(
self
,
self
,
point
:
SpaceTimeRecord
,
point
:
SpaceTimeRecord
,
dist
:
float
,
dist
:
float
,
t_dist
:
timedelta
,
t_dist
:
datetime
.
timedelta
,
)
->
bool
:
)
->
bool
:
"""
"""
Check if point is nearby the Rectangle
Check if point is nearby the Rectangle
...
@@ -192,42 +254,21 @@ class SpaceTimeRectangle:
...
@@ -192,42 +254,21 @@ class SpaceTimeRectangle:
----------
----------
point : SpaceTimeRecord
point : SpaceTimeRecord
dist : float,
dist : float,
t_dist : timedelta
t_dist :
datetime.
timedelta
Returns
Returns
-------
-------
bool : True if the point is <= dist + max(dist(centre, corners))
bool : True if the point is <= dist + max(dist(centre, corners))
"""
"""
if
(
if
(
point
.
datetime
-
t_dist
>
self
.
date
time
+
self
.
dt
/
2
point
.
datetime
-
t_dist
>
self
.
date
+
self
.
dt
/
2
or
point
.
datetime
+
t_dist
<
self
.
date
time
-
self
.
dt
/
2
or
point
.
datetime
+
t_dist
<
self
.
date
-
self
.
dt
/
2
):
):
return
False
return
False
# QUESTION: Is this sufficient? Possibly it is overkill
# QUESTION: Is this sufficient? Possibly it is overkill
corner_dist
=
max
(
haversine
(
self
.
lon
,
self
.
lat
,
self
.
lon
+
self
.
lon_range
/
2
,
self
.
lat
+
self
.
lat_range
/
2
,
),
haversine
(
self
.
lon
,
self
.
lat
,
self
.
lon
+
self
.
lon_range
/
2
,
self
.
lat
-
self
.
lat_range
/
2
,
),
)
if
(
self
.
lat
+
self
.
lat_range
/
2
)
*
(
self
.
lat
-
self
.
lat_range
/
2
)
<
0
:
corner_dist
=
max
(
corner_dist
,
haversine
(
self
.
lon
,
self
.
lat
,
self
.
lon
+
self
.
lon_range
/
2
,
0
),
)
return
(
return
(
haversine
(
self
.
lon
,
self
.
lat
,
point
.
lon
,
point
.
lat
)
haversine
(
self
.
lon
,
self
.
lat
,
point
.
lon
,
point
.
lat
)
<=
dist
+
corner
_dist
<=
dist
+
self
.
edge
_dist
)
)
...
@@ -241,7 +282,7 @@ class SpaceTimeEllipse:
...
@@ -241,7 +282,7 @@ class SpaceTimeEllipse:
Horizontal centre of the ellipse
Horizontal centre of the ellipse
lat : float
lat : float
Vertical centre of the ellipse
Vertical centre of the ellipse
datetime : datetime
datetime : datetime
.datetime
Datetime centre of the ellipse.
Datetime centre of the ellipse.
a : float
a : float
Length of the semi-major axis
Length of the semi-major axis
...
@@ -249,7 +290,7 @@ class SpaceTimeEllipse:
...
@@ -249,7 +290,7 @@ class SpaceTimeEllipse:
Length of the semi-minor axis
Length of the semi-minor axis
theta : float
theta : float
Angle of the semi-major axis from horizontal anti-clockwise in radians
Angle of the semi-major axis from horizontal anti-clockwise in radians
dt : timedelta
dt :
datetime.
timedelta
(full) time extent of the ellipse.
(full) time extent of the ellipse.
"""
"""
...
@@ -257,11 +298,11 @@ class SpaceTimeEllipse:
...
@@ -257,11 +298,11 @@ class SpaceTimeEllipse:
self
,
self
,
lon
:
float
,
lon
:
float
,
lat
:
float
,
lat
:
float
,
datetime
:
datetime
,
datetime
:
datetime
.
datetime
,
a
:
float
,
a
:
float
,
b
:
float
,
b
:
float
,
theta
:
float
,
theta
:
float
,
dt
:
timedelta
,
dt
:
datetime
.
timedelta
,
)
->
None
:
)
->
None
:
self
.
a
=
a
self
.
a
=
a
self
.
b
=
b
self
.
b
=
b
...
@@ -290,53 +331,28 @@ class SpaceTimeEllipse:
...
@@ -290,53 +331,28 @@ class SpaceTimeEllipse:
(
self
.
bearing
-
180
)
%
360
,
(
self
.
bearing
-
180
)
%
360
,
self
.
c
,
self
.
c
,
)
)
self
.
start
=
self
.
datetime
-
self
.
dt
/
2
self
.
end
=
self
.
datetime
+
self
.
dt
/
2
def
contains
(
self
,
point
:
SpaceTimeRecord
)
->
bool
:
def
contains
(
self
,
point
:
SpaceTimeRecord
)
->
bool
:
"""Test if a point is contained within the Ellipse"""
"""Test if a point is contained within the Ellipse"""
if
point
.
datetime
>
self
.
end
or
point
.
datetime
<
self
.
start
:
return
False
return
(
return
(
(
haversine
(
self
.
p1_lon
,
self
.
p1_lat
,
point
.
lon
,
point
.
lat
)
haversine
(
self
.
p1_lon
,
self
.
p1_lat
,
point
.
lon
,
point
.
lat
)
+
haversine
(
self
.
p2_lon
,
self
.
p2_lat
,
point
.
lon
,
point
.
lat
)
+
haversine
(
self
.
p2_lon
,
self
.
p2_lat
,
point
.
lon
,
point
.
lat
)
)
<=
2
*
self
.
a
)
<=
2
*
self
.
a
and
point
.
datetime
<=
self
.
datetime
+
self
.
dt
/
2
and
point
.
datetime
>=
self
.
datetime
-
self
.
dt
/
2
)
def
nearby_rect
(
self
,
rect
:
SpaceTimeRectangle
)
->
bool
:
def
nearby_rect
(
self
,
rect
:
SpaceTimeRectangle
)
->
bool
:
"""Test if a rectangle is near to the Ellipse"""
"""Test if a rectangle is near to the Ellipse"""
if
(
if
rect
.
start
>
self
.
end
or
rect
.
end
<
self
.
start
:
rect
.
datetime
-
rect
.
dt
/
2
>
self
.
datetime
+
self
.
dt
/
2
or
rect
.
datetime
+
rect
.
dt
/
2
<
self
.
datetime
-
self
.
dt
/
2
):
return
False
return
False
# TODO: Check corners, and 0 lat
# TODO: Check corners, and 0 lat
corner_dist
=
max
(
haversine
(
rect
.
lon
,
rect
.
lat
,
rect
.
lon
+
rect
.
lon_range
/
2
,
rect
.
lat
+
rect
.
lat_range
/
2
,
),
haversine
(
rect
.
lon
,
rect
.
lat
,
rect
.
lon
+
rect
.
lon_range
/
2
,
rect
.
lat
-
rect
.
lat_range
/
2
,
),
)
if
(
rect
.
lat
+
rect
.
lat_range
/
2
)
*
(
rect
.
lat
-
rect
.
lat_range
/
2
)
<
0
:
corner_dist
=
max
(
corner_dist
,
haversine
(
rect
.
lon
,
rect
.
lat
,
rect
.
lon
+
rect
.
lon_range
/
2
,
0
),
)
return
(
return
(
haversine
(
self
.
p1_lon
,
self
.
p1_lat
,
rect
.
lon
,
rect
.
lat
)
haversine
(
self
.
p1_lon
,
self
.
p1_lat
,
rect
.
lon
,
rect
.
lat
)
<=
corner
_dist
+
self
.
a
<=
rect
.
edge
_dist
+
self
.
a
and
haversine
(
self
.
p2_lon
,
self
.
p2_lat
,
rect
.
lon
,
rect
.
lat
)
and
haversine
(
self
.
p2_lon
,
self
.
p2_lat
,
rect
.
lon
,
rect
.
lat
)
<=
corner
_dist
+
self
.
a
<=
rect
.
edge
_dist
+
self
.
a
)
)
...
@@ -419,7 +435,7 @@ class OctTree:
...
@@ -419,7 +435,7 @@ class OctTree:
SpaceTimeRectangle
(
SpaceTimeRectangle
(
self
.
boundary
.
lon
-
self
.
boundary
.
lon_range
/
4
,
self
.
boundary
.
lon
-
self
.
boundary
.
lon_range
/
4
,
self
.
boundary
.
lat
+
self
.
boundary
.
lat_range
/
4
,
self
.
boundary
.
lat
+
self
.
boundary
.
lat_range
/
4
,
self
.
boundary
.
date
time
+
self
.
boundary
.
dt
/
4
,
self
.
boundary
.
date
+
self
.
boundary
.
dt
/
4
,
self
.
boundary
.
lon_range
/
2
,
self
.
boundary
.
lon_range
/
2
,
self
.
boundary
.
lat_range
/
2
,
self
.
boundary
.
lat_range
/
2
,
self
.
boundary
.
dt
/
2
,
self
.
boundary
.
dt
/
2
,
...
@@ -432,7 +448,7 @@ class OctTree:
...
@@ -432,7 +448,7 @@ class OctTree:
SpaceTimeRectangle
(
SpaceTimeRectangle
(
self
.
boundary
.
lon
+
self
.
boundary
.
lon_range
/
4
,
self
.
boundary
.
lon
+
self
.
boundary
.
lon_range
/
4
,
self
.
boundary
.
lat
+
self
.
boundary
.
lat_range
/
4
,
self
.
boundary
.
lat
+
self
.
boundary
.
lat_range
/
4
,
self
.
boundary
.
date
time
+
self
.
boundary
.
dt
/
4
,
self
.
boundary
.
date
+
self
.
boundary
.
dt
/
4
,
self
.
boundary
.
lon_range
/
2
,
self
.
boundary
.
lon_range
/
2
,
self
.
boundary
.
lat_range
/
2
,
self
.
boundary
.
lat_range
/
2
,
self
.
boundary
.
dt
/
2
,
self
.
boundary
.
dt
/
2
,
...
@@ -445,7 +461,7 @@ class OctTree:
...
@@ -445,7 +461,7 @@ class OctTree:
SpaceTimeRectangle
(
SpaceTimeRectangle
(
self
.
boundary
.
lon
-
self
.
boundary
.
lon_range
/
4
,
self
.
boundary
.
lon
-
self
.
boundary
.
lon_range
/
4
,
self
.
boundary
.
lat
-
self
.
boundary
.
lat_range
/
4
,
self
.
boundary
.
lat
-
self
.
boundary
.
lat_range
/
4
,
self
.
boundary
.
date
time
+
self
.
boundary
.
dt
/
4
,
self
.
boundary
.
date
+
self
.
boundary
.
dt
/
4
,
self
.
boundary
.
lon_range
/
2
,
self
.
boundary
.
lon_range
/
2
,
self
.
boundary
.
lat_range
/
2
,
self
.
boundary
.
lat_range
/
2
,
self
.
boundary
.
dt
/
2
,
self
.
boundary
.
dt
/
2
,
...
@@ -458,7 +474,7 @@ class OctTree:
...
@@ -458,7 +474,7 @@ class OctTree:
SpaceTimeRectangle
(
SpaceTimeRectangle
(
self
.
boundary
.
lon
+
self
.
boundary
.
lon_range
/
4
,
self
.
boundary
.
lon
+
self
.
boundary
.
lon_range
/
4
,
self
.
boundary
.
lat
-
self
.
boundary
.
lat_range
/
4
,
self
.
boundary
.
lat
-
self
.
boundary
.
lat_range
/
4
,
self
.
boundary
.
date
time
+
self
.
boundary
.
dt
/
4
,
self
.
boundary
.
date
+
self
.
boundary
.
dt
/
4
,
self
.
boundary
.
lon_range
/
2
,
self
.
boundary
.
lon_range
/
2
,
self
.
boundary
.
lat_range
/
2
,
self
.
boundary
.
lat_range
/
2
,
self
.
boundary
.
dt
/
2
,
self
.
boundary
.
dt
/
2
,
...
@@ -471,7 +487,7 @@ class OctTree:
...
@@ -471,7 +487,7 @@ class OctTree:
SpaceTimeRectangle
(
SpaceTimeRectangle
(
self
.
boundary
.
lon
-
self
.
boundary
.
lon_range
/
4
,
self
.
boundary
.
lon
-
self
.
boundary
.
lon_range
/
4
,
self
.
boundary
.
lat
+
self
.
boundary
.
lat_range
/
4
,
self
.
boundary
.
lat
+
self
.
boundary
.
lat_range
/
4
,
self
.
boundary
.
date
time
-
self
.
boundary
.
dt
/
4
,
self
.
boundary
.
date
-
self
.
boundary
.
dt
/
4
,
self
.
boundary
.
lon_range
/
2
,
self
.
boundary
.
lon_range
/
2
,
self
.
boundary
.
lat_range
/
2
,
self
.
boundary
.
lat_range
/
2
,
self
.
boundary
.
dt
/
2
,
self
.
boundary
.
dt
/
2
,
...
@@ -484,7 +500,7 @@ class OctTree:
...
@@ -484,7 +500,7 @@ class OctTree:
SpaceTimeRectangle
(
SpaceTimeRectangle
(
self
.
boundary
.
lon
+
self
.
boundary
.
lon_range
/
4
,
self
.
boundary
.
lon
+
self
.
boundary
.
lon_range
/
4
,
self
.
boundary
.
lat
+
self
.
boundary
.
lat_range
/
4
,
self
.
boundary
.
lat
+
self
.
boundary
.
lat_range
/
4
,
self
.
boundary
.
date
time
-
self
.
boundary
.
dt
/
4
,
self
.
boundary
.
date
-
self
.
boundary
.
dt
/
4
,
self
.
boundary
.
lon_range
/
2
,
self
.
boundary
.
lon_range
/
2
,
self
.
boundary
.
lat_range
/
2
,
self
.
boundary
.
lat_range
/
2
,
self
.
boundary
.
dt
/
2
,
self
.
boundary
.
dt
/
2
,
...
@@ -497,7 +513,7 @@ class OctTree:
...
@@ -497,7 +513,7 @@ class OctTree:
SpaceTimeRectangle
(
SpaceTimeRectangle
(
self
.
boundary
.
lon
-
self
.
boundary
.
lon_range
/
4
,
self
.
boundary
.
lon
-
self
.
boundary
.
lon_range
/
4
,
self
.
boundary
.
lat
-
self
.
boundary
.
lat_range
/
4
,
self
.
boundary
.
lat
-
self
.
boundary
.
lat_range
/
4
,
self
.
boundary
.
date
time
-
self
.
boundary
.
dt
/
4
,
self
.
boundary
.
date
-
self
.
boundary
.
dt
/
4
,
self
.
boundary
.
lon_range
/
2
,
self
.
boundary
.
lon_range
/
2
,
self
.
boundary
.
lat_range
/
2
,
self
.
boundary
.
lat_range
/
2
,
self
.
boundary
.
dt
/
2
,
self
.
boundary
.
dt
/
2
,
...
@@ -510,7 +526,7 @@ class OctTree:
...
@@ -510,7 +526,7 @@ class OctTree:
SpaceTimeRectangle
(
SpaceTimeRectangle
(
self
.
boundary
.
lon
+
self
.
boundary
.
lon_range
/
4
,
self
.
boundary
.
lon
+
self
.
boundary
.
lon_range
/
4
,
self
.
boundary
.
lat
-
self
.
boundary
.
lat_range
/
4
,
self
.
boundary
.
lat
-
self
.
boundary
.
lat_range
/
4
,
self
.
boundary
.
date
time
-
self
.
boundary
.
dt
/
4
,
self
.
boundary
.
date
-
self
.
boundary
.
dt
/
4
,
self
.
boundary
.
lon_range
/
2
,
self
.
boundary
.
lon_range
/
2
,
self
.
boundary
.
lat_range
/
2
,
self
.
boundary
.
lat_range
/
2
,
self
.
boundary
.
dt
/
2
,
self
.
boundary
.
dt
/
2
,
...
@@ -522,7 +538,7 @@ class OctTree:
...
@@ -522,7 +538,7 @@ class OctTree:
self
.
divided
=
True
self
.
divided
=
True
def
_datetime_is_numeric
(
self
)
->
bool
:
def
_datetime_is_numeric
(
self
)
->
bool
:
return
not
isinstance
(
self
.
boundary
.
date
time
,
datetime
)
return
not
isinstance
(
self
.
boundary
.
date
,
datetime
)
def
insert
(
self
,
point
:
SpaceTimeRecord
)
->
bool
:
def
insert
(
self
,
point
:
SpaceTimeRecord
)
->
bool
:
"""
"""
...
@@ -618,7 +634,7 @@ class OctTree:
...
@@ -618,7 +634,7 @@ class OctTree:
self
,
self
,
point
:
SpaceTimeRecord
,
point
:
SpaceTimeRecord
,
dist
:
float
,
dist
:
float
,
t_dist
:
timedelta
,
t_dist
:
datetime
.
timedelta
,
points
:
SpaceTimeRecords
|
None
=
None
,
points
:
SpaceTimeRecords
|
None
=
None
,
)
->
SpaceTimeRecords
:
)
->
SpaceTimeRecords
:
"""
"""
...
@@ -637,7 +653,7 @@ class OctTree:
...
@@ -637,7 +653,7 @@ class OctTree:
The distance for comparison. Note that Haversine distance is used
The distance for comparison. Note that Haversine distance is used
as the distance metric as the query SpaceTimeRecord and OctTree are
as the distance metric as the query SpaceTimeRecord and OctTree are
assumed to lie on the surface of Earth.
assumed to lie on the surface of Earth.
t_dist : timedelta
t_dist :
datetime.
timedelta
Max time gap between SpaceTimeRecords within the OctTree and the
Max time gap between SpaceTimeRecords within the OctTree and the
query SpaceTimeRecord. Can be numeric if the OctTree boundaries,
query SpaceTimeRecord. Can be numeric if the OctTree boundaries,
SpaceTimeRecords, and query SpaceTimeRecord have numeric datetime
SpaceTimeRecords, and query SpaceTimeRecord have numeric datetime
...
...
This diff is collapsed.
Click to expand it.
GeoSpatialTools/quadtree.py
View file @
762dfafa
...
@@ -3,6 +3,7 @@ Constuctors for QuadTree classes that can decrease the number of comparisons
...
@@ -3,6 +3,7 @@ Constuctors for QuadTree classes that can decrease the number of comparisons
for detecting nearby records for example
for detecting nearby records for example
"""
"""
from
dataclasses
import
dataclass
from
datetime
import
datetime
from
datetime
import
datetime
from
.distance_metrics
import
haversine
,
destination
from
.distance_metrics
import
haversine
,
destination
from
.utils
import
LatitudeError
from
.utils
import
LatitudeError
...
@@ -82,6 +83,7 @@ class Record:
...
@@ -82,6 +83,7 @@ class Record:
return
haversine
(
self
.
lon
,
self
.
lat
,
other
.
lon
,
other
.
lat
)
return
haversine
(
self
.
lon
,
self
.
lat
,
other
.
lon
,
other
.
lat
)
@
dataclass
class
Rectangle
:
class
Rectangle
:
"""
"""
A simple Rectangle class
A simple Rectangle class
...
@@ -98,46 +100,100 @@ class Rectangle:
...
@@ -98,46 +100,100 @@ class Rectangle:
Height of the rectangle
Height of the rectangle
"""
"""
def
__init__
(
lon
:
float
self
,
lat
:
float
lon
:
float
,
lon_range
:
float
lat
:
float
,
lat_range
:
float
lon_range
:
float
,
lat_range
:
float
,
)
->
None
:
self
.
lon
=
lon
self
.
lat
=
lat
self
.
lon_range
=
lon_range
self
.
lat_range
=
lat_range
def
__str__
(
self
)
->
str
:
def
__post_init__
(
self
):
return
f
"Rectangle(x =
{
self
.
lon
}
, y =
{
self
.
lat
}
, w =
{
self
.
lon_range
}
, h =
{
self
.
lat_range
}
)"
if
self
.
lon
>
180
:
self
.
lon
-=
360
if
self
.
lat
>
90
or
self
.
lat
<
-
90
:
raise
LatitudeError
(
f
"Central latitude value out of range
{
self
.
lat
}
, "
+
"should be between -90, 90 degrees"
)
def
__eq__
(
self
,
other
:
object
)
->
bool
:
@
property
return
(
def
west
(
self
)
->
float
:
isinstance
(
other
,
Rectangle
)
"""Western boundary of the Rectangle"""
and
self
.
lon
==
other
.
lon
return
(((
self
.
lon
-
self
.
lon_range
/
2
)
+
540
)
%
360
)
-
180
and
self
.
lat
==
other
.
lat
and
self
.
lon_range
==
other
.
lon_range
@
property
and
self
.
lat_range
==
other
.
lat_range
def
east
(
self
)
->
float
:
"""Eastern boundary of the Rectangle"""
return
(((
self
.
lon
+
self
.
lon_range
/
2
)
+
540
)
%
360
)
-
180
@
property
def
north
(
self
)
->
float
:
"""Northern boundary of the Rectangle"""
north
=
self
.
lat
+
self
.
lat_range
/
2
if
north
>
90
:
raise
LatitudeError
(
"Rectangle crosses north pole - Use two Rectangles"
)
return
north
@
property
def
south
(
self
)
->
float
:
"""Southern boundary of the Rectangle"""
south
=
self
.
lat
-
self
.
lat_range
/
2
if
south
<
-
90
:
raise
LatitudeError
(
"Rectangle crosses south pole - Use two Rectangles"
)
return
south
@
property
def
edge_dist
(
self
)
->
float
:
"""Approximate maximum distance from the centre to an edge"""
corner_dist
=
max
(
haversine
(
self
.
lon
,
self
.
lat
,
self
.
east
,
self
.
north
),
haversine
(
self
.
lon
,
self
.
lat
,
self
.
east
,
self
.
south
),
)
)
if
self
.
east
*
self
.
west
<
0
:
corner_dist
=
max
(
corner_dist
,
haversine
(
self
.
lon
,
self
.
lat
,
self
.
east
,
0
),
)
return
corner_dist
def
_test_east_west
(
self
,
lon
:
float
)
->
bool
:
if
self
.
lon_range
>=
360
:
# Rectangle encircles earth
return
True
if
self
.
east
>
self
.
lon
and
self
.
west
<
self
.
lon
:
return
lon
<=
self
.
east
and
lon
>=
self
.
west
if
self
.
east
<
self
.
lon
:
return
not
(
lon
>
self
.
east
and
lon
<
self
.
west
)
if
self
.
west
>
self
.
lon
:
return
not
(
lon
<
self
.
east
and
lon
>
self
.
west
)
return
False
def
_test_north_south
(
self
,
lat
:
float
)
->
bool
:
return
lat
<=
self
.
north
and
lat
>=
self
.
south
def
contains
(
self
,
point
:
Record
)
->
bool
:
def
contains
(
self
,
point
:
Record
)
->
bool
:
"""Test if a point is contained within the Rectangle"""
"""Test if a point is contained within the Rectangle"""
return
(
return
self
.
_test_north_south
(
point
.
lat
)
and
self
.
_test_east_west
(
point
.
lon
<=
self
.
lon
+
self
.
lon_range
/
2
point
.
lon
and
point
.
lon
>=
self
.
lon
-
self
.
lon_range
/
2
and
point
.
lat
<=
self
.
lat
+
self
.
lat_range
/
2
and
point
.
lat
>=
self
.
lat
-
self
.
lat_range
/
2
)
)
def
intersects
(
self
,
other
:
object
)
->
bool
:
def
intersects
(
self
,
other
:
object
)
->
bool
:
"""Test if another Rectangle object intersects this Rectangle"""
"""Test if another Rectangle object intersects this Rectangle"""
return
isinstance
(
other
,
Rectangle
)
and
not
(
if
not
isinstance
(
other
,
Rectangle
):
self
.
lon
-
self
.
lon_range
/
2
>
other
.
lon
+
other
.
lon_range
/
2
raise
TypeError
(
or
self
.
lon
+
self
.
lon_range
/
2
<
other
.
lon
-
other
.
lon_range
/
2
f
"other must be a Rectangle class, got
{
type
(
other
)
}
"
or
self
.
lat
-
self
.
lat_range
/
2
>
other
.
lat
+
other
.
lat_range
/
2
)
or
self
.
lat
+
self
.
lat_range
/
2
<
other
.
lat
-
other
.
lat_range
/
2
if
other
.
south
>
self
.
north
:
# Other is fully north of self
return
False
if
other
.
north
<
self
.
south
:
# Other is fully south of self
return
False
# Handle east / west edges
return
self
.
_test_east_west
(
other
.
west
)
or
self
.
_test_east_west
(
other
.
east
)
)
def
nearby
(
def
nearby
(
...
@@ -147,30 +203,9 @@ class Rectangle:
...
@@ -147,30 +203,9 @@ class Rectangle:
)
->
bool
:
)
->
bool
:
"""Check if point is nearby the Rectangle"""
"""Check if point is nearby the Rectangle"""
# QUESTION: Is this sufficient? Possibly it is overkill
# QUESTION: Is this sufficient? Possibly it is overkill
corner_dist
=
max
(
haversine
(
self
.
lon
,
self
.
lat
,
self
.
lon
+
self
.
lon_range
/
2
,
self
.
lat
+
self
.
lat_range
/
2
,
),
haversine
(
self
.
lon
,
self
.
lat
,
self
.
lon
+
self
.
lon_range
/
2
,
self
.
lat
-
self
.
lat_range
/
2
,
),
)
if
(
self
.
lat
+
self
.
lat_range
/
2
)
*
(
self
.
lat
-
self
.
lat_range
/
2
)
<
0
:
corner_dist
=
max
(
corner_dist
,
haversine
(
self
.
lon
,
self
.
lat
,
self
.
lon
+
self
.
lon_range
/
2
,
0
),
)
return
(
return
(
haversine
(
self
.
lon
,
self
.
lat
,
point
.
lon
,
point
.
lat
)
haversine
(
self
.
lon
,
self
.
lat
,
point
.
lon
,
point
.
lat
)
<=
dist
+
corner
_dist
<=
dist
+
self
.
edge
_dist
)
)
...
@@ -235,33 +270,11 @@ class Ellipse:
...
@@ -235,33 +270,11 @@ class Ellipse:
def
nearby_rect
(
self
,
rect
:
Rectangle
)
->
bool
:
def
nearby_rect
(
self
,
rect
:
Rectangle
)
->
bool
:
"""Test if a rectangle is near to the Ellipse"""
"""Test if a rectangle is near to the Ellipse"""
# TODO: Check corners, and 0 lat
corner_dist
=
max
(
haversine
(
rect
.
lon
,
rect
.
lat
,
rect
.
lon
+
rect
.
lon_range
/
2
,
rect
.
lat
+
rect
.
lat_range
/
2
,
),
haversine
(
rect
.
lon
,
rect
.
lat
,
rect
.
lon
+
rect
.
lon_range
/
2
,
rect
.
lat
-
rect
.
lat_range
/
2
,
),
)
if
(
rect
.
lat
+
rect
.
lat_range
/
2
)
*
(
rect
.
lat
-
rect
.
lat_range
/
2
)
<
0
:
corner_dist
=
max
(
corner_dist
,
haversine
(
rect
.
lon
,
rect
.
lat
,
rect
.
lon
+
rect
.
lon_range
/
2
,
0
),
)
return
(
return
(
haversine
(
self
.
p1_lon
,
self
.
p1_lat
,
rect
.
lon
,
rect
.
lat
)
haversine
(
self
.
p1_lon
,
self
.
p1_lat
,
rect
.
lon
,
rect
.
lat
)
<=
corner
_dist
+
self
.
a
<=
rect
.
edge
_dist
+
self
.
a
and
haversine
(
self
.
p2_lon
,
self
.
p2_lat
,
rect
.
lon
,
rect
.
lat
)
and
haversine
(
self
.
p2_lon
,
self
.
p2_lat
,
rect
.
lon
,
rect
.
lat
)
<=
corner
_dist
+
self
.
a
<=
rect
.
edge
_dist
+
self
.
a
)
)
...
...
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment