diff --git a/GeoSpatialTools/distance_metrics.py b/GeoSpatialTools/distance_metrics.py
index 9d148c2f3af6369a85cd35de4e048f362356a0eb..339b0fcd9d32dc7e20fe3c5b9eae0e3f41ce5c91 100644
--- a/GeoSpatialTools/distance_metrics.py
+++ b/GeoSpatialTools/distance_metrics.py
@@ -4,6 +4,7 @@ navigational information to DataFrames.
 """
 
 from math import acos, asin, atan2, cos, sin, degrees, radians, sqrt
+from typing import Tuple
 
 
 def gcd_slc(
@@ -124,7 +125,7 @@ def bearing(
 
 def destination(
     lon: float, lat: float, bearing: float, distance: float
-) -> tuple[float, float]:
+) -> Tuple[float, float]:
     """
     Compute destination of a great circle path.
 
@@ -173,7 +174,7 @@ def midpoint(
     lat0: float,
     lon1: float,
     lat1: float,
-) -> tuple[float, float]:
+) -> Tuple[float, float]:
     """
     Compute the midpoint of a great circle track
 
diff --git a/GeoSpatialTools/kdtree.py b/GeoSpatialTools/kdtree.py
index 7e90068de9c4661ffdab91478381decfcfb59a5e..7da9df63688f8d7c4f7e4c954e302ac89a6a3acb 100644
--- a/GeoSpatialTools/kdtree.py
+++ b/GeoSpatialTools/kdtree.py
@@ -5,6 +5,7 @@ Useful tool for quickly searching for nearest neighbours.
 
 from . import Record
 from numpy import inf
+from typing import List, Optional, Tuple
 
 
 class KDTree:
@@ -33,7 +34,7 @@ class KDTree:
     """
 
     def __init__(
-        self, points: list[Record], depth: int = 0, max_depth: int = 20
+        self, points: List[Record], depth: int = 0, max_depth: int = 20
     ) -> None:
         self.depth = depth
         n_points = len(points)
@@ -110,7 +111,7 @@ class KDTree:
                 return True
         return False
 
-    def query(self, point) -> tuple[list[Record], float]:
+    def query(self, point) -> Tuple[List[Record], float]:
         """Find the nearest Record within the KDTree to a query Record"""
         if point.lon < 0:
             point2 = Record(point.lon + 360, point.lat, fix_lon=False)
@@ -127,9 +128,9 @@ class KDTree:
     def _query(
         self,
         point: Record,
-        current_best: list[Record] | None = None,
+        current_best: Optional[List[Record]] = None,
         best_distance: float = inf,
-    ) -> tuple[list[Record], float]:
+    ) -> Tuple[List[Record], float]:
         if current_best is None:
             current_best = list()
         if not self.split:
diff --git a/GeoSpatialTools/neighbours.py b/GeoSpatialTools/neighbours.py
index a1b165649d71b8cc8fdd75416bd2fd7cceeeb1ab..02143fca57dddaeb8402c0f44984f1825e29f672 100644
--- a/GeoSpatialTools/neighbours.py
+++ b/GeoSpatialTools/neighbours.py
@@ -2,7 +2,7 @@
 
 from numpy import argmin
 from bisect import bisect
-from typing import TypeVar
+from typing import List, TypeVar, Union
 from datetime import date, datetime
 from warnings import warn
 
@@ -22,7 +22,7 @@ class SortedError(Exception):
     pass
 
 
-def _find_nearest(vals: list[Numeric], test: Numeric) -> int:
+def _find_nearest(vals: List[Numeric], test: Numeric) -> int:
     i = bisect(vals, test)  # Position that test would be inserted
 
     # Handle edges
@@ -36,10 +36,10 @@ def _find_nearest(vals: list[Numeric], test: Numeric) -> int:
 
 
 def find_nearest(
-    vals: list[Numeric],
-    test: list[Numeric] | Numeric,
+    vals: List[Numeric],
+    test: Union[List[Numeric], Numeric],
     check_sorted: bool = True,
-) -> list[int] | int:
+) -> Union[List[int], int]:
     """
     Find the nearest value in a list of values for each test value.
 
diff --git a/GeoSpatialTools/octtree.py b/GeoSpatialTools/octtree.py
index e0eac8aa9f8cbc43164c3ac45ef921cb2fa71df4..d989bbc9d927b149bc778603e8b399ca72cb67f8 100644
--- a/GeoSpatialTools/octtree.py
+++ b/GeoSpatialTools/octtree.py
@@ -1,4 +1,5 @@
 from dataclasses import dataclass
+from typing import List, Optional
 import datetime
 from .distance_metrics import haversine, destination
 from .utils import LatitudeError, DateWarning
@@ -45,7 +46,7 @@ class SpaceTimeRecord:
         lon: float,
         lat: float,
         datetime: datetime.datetime,
-        uid: str | None = None,
+        uid: Optional[str] = None,
         fix_lon: bool = True,
         **data,
     ) -> None:
@@ -80,7 +81,7 @@ class SpaceTimeRecord:
         )
 
 
-class SpaceTimeRecords(list[SpaceTimeRecord]):
+class SpaceTimeRecords(List[SpaceTimeRecord]):
     """List of SpaceTimeRecords"""
 
 
@@ -402,7 +403,7 @@ class OctTree:
         boundary: SpaceTimeRectangle,
         capacity: int = 5,
         depth: int = 0,
-        max_depth: int | None = None,
+        max_depth: Optional[int] = None,
     ) -> None:
         self.boundary = boundary
         self.capacity = capacity
@@ -584,7 +585,7 @@ class OctTree:
     def query(
         self,
         rect: SpaceTimeRectangle,
-        points: SpaceTimeRecords | None = None,
+        points: Optional[SpaceTimeRecords] = None,
     ) -> SpaceTimeRecords:
         """Get points that fall in a SpaceTimeRectangle"""
         if not points:
@@ -611,7 +612,7 @@ class OctTree:
     def query_ellipse(
         self,
         ellipse: SpaceTimeEllipse,
-        points: SpaceTimeRecords | None = None,
+        points: Optional[SpaceTimeRecords] = None,
     ) -> SpaceTimeRecords:
         """Get points that fall in an ellipse."""
         if not points:
@@ -640,7 +641,7 @@ class OctTree:
         point: SpaceTimeRecord,
         dist: float,
         t_dist: datetime.timedelta,
-        points: SpaceTimeRecords | None = None,
+        points: Optional[SpaceTimeRecords] = None,
     ) -> SpaceTimeRecords:
         """
         Get all points that are nearby another point.
diff --git a/GeoSpatialTools/quadtree.py b/GeoSpatialTools/quadtree.py
index 152b90b5f1c40d8d77e1fd7865ebe0005c1c0385..d24b4befef9cb22659f0ece485b67ee8f8b0e564 100644
--- a/GeoSpatialTools/quadtree.py
+++ b/GeoSpatialTools/quadtree.py
@@ -4,6 +4,7 @@ for detecting nearby records for example
 """
 
 from dataclasses import dataclass
+from typing import List, Optional
 from datetime import datetime
 from .distance_metrics import haversine, destination
 from .utils import LatitudeError
@@ -41,8 +42,8 @@ class Record:
         self,
         lon: float,
         lat: float,
-        datetime: datetime | None = None,
-        uid: str | None = None,
+        datetime: Optional[datetime] = None,
+        uid: Optional[str] = None,
         fix_lon: bool = True,
         **data,
     ) -> None:
@@ -305,13 +306,13 @@ class QuadTree:
         boundary: Rectangle,
         capacity: int = 5,
         depth: int = 0,
-        max_depth: int | None = None,
+        max_depth: Optional[int] = None,
     ) -> None:
         self.boundary = boundary
         self.capacity = capacity
         self.depth = depth
         self.max_depth = max_depth
-        self.points: list[Record] = list()
+        self.points: List[Record] = list()
         self.divided: bool = False
         return None
 
@@ -406,8 +407,8 @@ class QuadTree:
     def query(
         self,
         rect: Rectangle,
-        points: list[Record] | None = None,
-    ) -> list[Record]:
+        points: Optional[List[Record]] = None,
+    ) -> List[Record]:
         """Get points that fall in a rectangle"""
         if not points:
             points = list()
@@ -429,8 +430,8 @@ class QuadTree:
     def query_ellipse(
         self,
         ellipse: Ellipse,
-        points: list[Record] | None = None,
-    ) -> list[Record]:
+        points: Optional[List[Record]] = None,
+    ) -> List[Record]:
         """Get points that fall in an ellipse."""
         if not points:
             points = list()
@@ -453,8 +454,8 @@ class QuadTree:
         self,
         point: Record,
         dist: float,
-        points: list[Record] | None = None,
-    ) -> list[Record]:
+        points: Optional[List[Record]] = None,
+    ) -> List[Record]:
         """Get all points that are nearby another point"""
         if not points:
             points = list()
diff --git a/README.md b/README.md
index 2e3f06df47d8e4425cbc8e8d1b64b7960b281fd8..20d6a9c940c27c508b851d4891db322c25235925 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,7 @@
 
 Python module containing useful functions and classes for Spatial Analysis.
 
-Tested on Python versions 3.11, 3.12, and 3.13. Not expected to work with Python 3.9 or below due
-to usage of type annotations.
+Tested on Python versions 3.9 to 3.13.
 
 ## Installation
 
diff --git a/pyproject.toml b/pyproject.toml
index fc55ddc101d8761d350e550cb6d6ce002417e069..210f5c5949fabce2db082b41e60d65fb37a32ef7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -16,7 +16,7 @@ version = "0.6.0"
 dependencies = [
     "numpy",
 ]
-requires-python = ">=3.11"
+requires-python = ">=3.9"
 authors = [
     {name = "Joseph Siddons", email = "josidd@noc.ac.uk"},
     {name = "Richard Cornes", email = "rcornes@noc.ac.uk"},