Commit e606e155 authored by Joseph Siddons's avatar Joseph Siddons
Browse files

Merge branch '7-add-ci-cd' into 'main'

Resolve "Add CI/CD"

Closes #7

See merge request nocsurfaceprocesses/geospatialtools!25
No related merge requests found
Pipeline #267676 canceled with stages
[codespell]
# Ignore long base64 - e.g. images in notebooks
ignore-regex = [A-Za-z0-9+/]{100,}
skip = "./docs/_build"
count = true
quiet-level = 3
...@@ -25,7 +25,7 @@ share/python-wheels/ ...@@ -25,7 +25,7 @@ share/python-wheels/
.installed.cfg .installed.cfg
*.egg *.egg
MANIFEST MANIFEST
uv.lock .DS_Store
# PyInstaller # PyInstaller
# Usually these files are written by a python script from a template # Usually these files are written by a python script from a template
...@@ -73,6 +73,7 @@ instance/ ...@@ -73,6 +73,7 @@ instance/
docs/_build/ docs/_build/
docs/_static/ docs/_static/
docs/_templates/ docs/_templates/
docs/*.tex
# PyBuilder # PyBuilder
.pybuilder/ .pybuilder/
......
variables:
UV_VERSION: "0.5"
PYTHON_VERSION: "3.12"
BASE_LAYER: "bookworm-slim"
UV_CACHE_DIR: ".uv-cache"
UV_SYSTEM_PYTHON: "1"
stages:
- build
- test
- lint
pre-commit:
stage: build
image: python:3.11
script:
- pip install pre-commit
- pre-commit run --all-files
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
pytest:
stage: test
image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-$BASE_LAYER
cache:
- key:
files:
- uv.lock
paths:
- $UV_CACHE_DIR
script:
- uv --version
- uv sync --all-extras
- uv pip list
- uv run pytest test
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
ruff:
stage: lint
interruptible: true
image:
name: ghcr.io/astral-sh/ruff:0.9.9-alpine
before_script:
- cd $CI_PROJECT_DIR
- ruff --version
script:
- ruff check --output-format=gitlab > code-quality-report.json
artifacts:
reports:
codequality: $CI_PROJECT_DIR/code-quality-report.json
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.5
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: https://github.com/codespell-project/codespell
rev: v2.3.0
hooks:
- id: codespell
...@@ -6,6 +6,7 @@ Contributors to this version: Joseph Siddons (@josidd) ...@@ -6,6 +6,7 @@ Contributors to this version: Joseph Siddons (@josidd)
### Internal changes ### Internal changes
* Added CI/CD scripts for GitLab (!25).
* Added changelog (!26). * Added changelog (!26).
## 0.11.2 (2025-02-27) ## 0.11.2 (2025-02-27)
......
...@@ -15,3 +15,102 @@ Please write tests for your code, add these to the `test` directory. I use `pyte ...@@ -15,3 +15,102 @@ Please write tests for your code, add these to the `test` directory. I use `pyte
## Issues ## Issues
Please file issues as they arise. Describe the problem, the steps to reproduce, and provide any output. Please file issues as they arise. Describe the problem, the steps to reproduce, and provide any output.
# Contributing to `GeoSpatialTools`
To contribute to this package you will need to be a member of NOC GitLab & have access permissions
to this repository - if you're able to read this then you have access!
If you wish to contribute please make sure you are working on your own branches (not main), ideally
you should work on your own fork. If you wish to work on a particular module you could name your
branch `module-user` where `module` would be replaced by the name of the module you are working on,
and `user` would be your user name. However you can name your branch as you see fit, but it is a
good idea to name it something that relates to what you are working on. If you are working on an
issue please reference the issue number in the branch name and associated Merge request. It is
generally easier to make a merge request and create a branch from the issue.
If you wish to merge to `main` please create a merge request and assign it to `josidd`,
and/or `ricorne` - either to perform the merge and/or review/approve the request. Please provide a
summary of the main changes that you have made so that there is context for us to review the
changes.
## Changelog
The changelog is `CHANGES.md`. Please add your changes to the changelog in your merge request.
## Commit Messages
We are trying to use a consistent and informative approach for writing commit messages in this
repository. We have adopted the [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/)
standard for commit messages. Whilst we won't enforce this standard upon others, we do recommend the
approach. Otherwise please ensure that your messages are descriptive and not just `changes` or
similar.
## Development Instructions
We recommend [uv](https://docs.astral.sh/uv/) for development purposes.
Clone the repository and create your development branch
```bash
git clone git@git.noc.ac.uk:noc_surface_processes/geospatialtools.git /path/to/geospatialtools
cd /path/to/geospatialtools
git checkout -b new-branch-name # if not a new branch exclude the '-b'
```
Create a virtual environment and install the dependencies
```bash
uv venv --python 3.12 # recommended version >= 3.9 is supported
source .venv/bin/activate # assuming bash or zsh
```
To install the dependencies run:
```bash
uv sync
```
Or to install all development dependencies and dependencies run:
```bash
uv sync --extra all --dev
```
## Standards
We recommend the use of [ruff](https://docs.astral.sh/ruff/) as a linter/formatter. The
`pyproject.toml` file includes all the settings for `ruff` for `GeoSpatialTools`.
```bash
uvx ruff check
uvx ruff check --fix
uvx ruff format
```
[codespell](https://github.com/codespell-project/codespell) is also used to check spelling/bad
names.
We use [pre-commit](https://pre-commit.com/) as part of out CI/CD processing.
## Tests
If you create new functionality please write and perform unit-tests on your code. The current
implementation of `GeoSpatialTools` uses the `pytest` library.
New tests do not need to be comprehensive, but I likely won't merge if your changes fails testing,
especially the pre-existing tests. You will need to include (and reference) any data that is
needed for testing.
We have a CI/CD pipeline that will automatically implement testing as part of merge requests.
We welcome additions/improvements to the current tests. New python test files should be placed in
the `test` directory and filenames must be prefixed with `test_`.
To perform tests you will need to have the environment set-up and active. Then run:
```
uv run pytest test/test_*.py
```
from the main/top directory for the repository.
...@@ -6,6 +6,7 @@ comparisons between GreatCircle objects. ...@@ -6,6 +6,7 @@ comparisons between GreatCircle objects.
""" """
import numpy as np import numpy as np
from typing import Optional, Tuple
from .distance_metrics import bearing, gcd_slc from .distance_metrics import bearing, gcd_slc
...@@ -15,7 +16,7 @@ def cartesian_to_lonlat( ...@@ -15,7 +16,7 @@ def cartesian_to_lonlat(
y: float, y: float,
z: float, z: float,
to_radians: bool = False, to_radians: bool = False,
) -> tuple[float, float]: ) -> Tuple[float, float]:
""" """
Get lon, and lat from cartesian coordinates. Get lon, and lat from cartesian coordinates.
...@@ -54,7 +55,7 @@ def polar_to_cartesian( ...@@ -54,7 +55,7 @@ def polar_to_cartesian(
R: float = 6371, R: float = 6371,
to_radians: bool = True, to_radians: bool = True,
normalised: bool = True, normalised: bool = True,
) -> tuple[float, float, float]: ) -> Tuple[float, float, float]:
""" """
Convert from polars coordinates to cartesian. Convert from polars coordinates to cartesian.
...@@ -196,7 +197,7 @@ class GreatCircle: ...@@ -196,7 +197,7 @@ class GreatCircle:
def intersection( def intersection(
self, other: object, epsilon: float = 0.01 self, other: object, epsilon: float = 0.01
) -> tuple[float, float] | None: ) -> Optional[Tuple[float, float]]:
""" """
Determine intersection position with another GreatCircle. Determine intersection position with another GreatCircle.
...@@ -241,7 +242,7 @@ class GreatCircle: ...@@ -241,7 +242,7 @@ class GreatCircle:
self, self,
other: object, other: object,
epsilon: float = 0.01, epsilon: float = 0.01,
) -> float | None: ) -> Optional[float]:
""" """
Get angle of intersection with another GreatCircle. Get angle of intersection with another GreatCircle.
......
...@@ -20,7 +20,7 @@ class KDTree: ...@@ -20,7 +20,7 @@ class KDTree:
This implementation is a _balanced_ KDTree, each leaf node should have the This implementation is a _balanced_ KDTree, each leaf node should have the
same number of points (or differ by 1 depending on the number of points same number of points (or differ by 1 depending on the number of points
the KDTree is intialised with). the KDTree is initialised with).
The KDTree partitions in each of the lon and lat dimensions alternatively The KDTree partitions in each of the lon and lat dimensions alternatively
in sequence by splitting at the median of the dimension of the points in sequence by splitting at the median of the dimension of the points
...@@ -34,7 +34,7 @@ class KDTree: ...@@ -34,7 +34,7 @@ class KDTree:
The current depth of the KDTree, you should set this to 0, it is used The current depth of the KDTree, you should set this to 0, it is used
internally. internally.
max_depth : int max_depth : int
The maximium depth of the KDTree. The leaf nodes will have depth no The maximum depth of the KDTree. The leaf nodes will have depth no
larger than this value. Leaf nodes will not be created if there is larger than this value. Leaf nodes will not be created if there is
only 1 point in the branch. only 1 point in the branch.
""" """
......
""" """
OctTree OctTree
------- -------
Constuctors for OctTree classes that can decrease the number of comparisons Constructors for OctTree classes that can decrease the number of comparisons
for detecting nearby records for example. This is an implementation that uses for detecting nearby records for example. This is an implementation that uses
Haversine distances for comparisons between records for identification of Haversine distances for comparisons between records for identification of
neighbours. neighbours.
...@@ -18,14 +18,14 @@ from warnings import warn ...@@ -18,14 +18,14 @@ from warnings import warn
class SpaceTimeRecord: class SpaceTimeRecord:
""" """
ICOADS Record class. SpaceTimeRecord class.
This is a simple instance of an ICOARDS record, it requires position and This is a simple instance of a record, it requires position and temporal
temporal data. It can optionally include a UID and extra data. data. It can optionally include a UID and extra data.
The temporal component was designed to use `datetime` values, however all The temporal component was designed to use `datetime` values, however all
methods will work with numeric datetime information - for example a pentad, methods will work with numeric datetime information - for example a pentad,
timestamp, julian day, etc. Note that any uses within an OctTree and time-stamp, Julian day, etc. Note that any uses within an OctTree and
SpaceTimeRectangle must also have timedelta values replaced with numeric SpaceTimeRectangle must also have timedelta values replaced with numeric
ranges in this case. ranges in this case.
......
""" """
QuadTree QuadTree
-------- --------
Constuctors for QuadTree classes that can decrease the number of comparisons Constructors for QuadTree classes that can decrease the number of comparisons
for detecting nearby records for example. This is an implementation that uses for detecting nearby records for example. This is an implementation that uses
Haversine distances for comparisons between records for identification of Haversine distances for comparisons between records for identification of
neighbours. neighbours.
...@@ -17,9 +17,9 @@ from math import degrees, sqrt ...@@ -17,9 +17,9 @@ from math import degrees, sqrt
class Record: class Record:
""" """
ICOADS Record class Record class
This is a simple instance of an ICOARDS record, it requires position data. This is a simple instance of a record, it requires position data.
It can optionally include datetime, a UID, and extra data passed as It can optionally include datetime, a UID, and extra data passed as
keyword arguments. keyword arguments.
......
...@@ -8,6 +8,6 @@ class LatitudeError(ValueError): ...@@ -8,6 +8,6 @@ class LatitudeError(ValueError):
class DateWarning(Warning): class DateWarning(Warning):
"""Warnning for Datetime Value""" """Warning for Datetime Value"""
pass pass
...@@ -393,5 +393,3 @@ the avoidance of doubt, this paragraph does not form part of the ...@@ -393,5 +393,3 @@ the avoidance of doubt, this paragraph does not form part of the
public licenses. public licenses.
Creative Commons may be contacted at creativecommons.org. Creative Commons may be contacted at creativecommons.org.
...@@ -81,11 +81,11 @@ N_samples = 1000 ...@@ -81,11 +81,11 @@ N_samples = 1000
records: list[Record] = [Record(choice(lon_range), choice(lat_range)) for _ in range(N_samples)] records: list[Record] = [Record(choice(lon_range), choice(lat_range)) for _ in range(N_samples)]
# Construct Tree # Construct Tree
kt = KDTree(records) kdtree = KDTree(records)
test_value: Record = Record(lon=47.6, lat=-31.1) test_value: Record = Record(lon=47.6, lat=-31.1)
neighbours: list[Record] = [] neighbours: list[Record] = []
neighbours, dist = kt.query(test_value) neighbours, dist = kdtree.query(test_value)
``` ```
### Points within distance (2d \& 3d) ### Points within distance (2d \& 3d)
...@@ -126,16 +126,16 @@ N_samples = 1000 ...@@ -126,16 +126,16 @@ N_samples = 1000
# Construct Tree # Construct Tree
boundary = Rectangle(-180, 180, -90, 90) # Full domain boundary = Rectangle(-180, 180, -90, 90) # Full domain
qt = QuadTree(boundary) quadtree = QuadTree(boundary)
records: list[Record] = [Record(choice(lon_range), choice(lat_range)) for _ in range(N_samples)] records: list[Record] = [Record(choice(lon_range), choice(lat_range)) for _ in range(N_samples)]
for record in records: for record in records:
qt.insert(record) quadtree.insert(record)
test_value: Record = Record(lon=47.6, lat=-31.1) test_value: Record = Record(lon=47.6, lat=-31.1)
dist: float = 340 # km dist: float = 340 # km
neighbours: list[Record] = qt.nearby_points(test_value, dist) neighbours: list[Record] = quadtree.nearby_points(test_value, dist)
``` ```
#### OctTree - 3d QuadTree #### OctTree - 3d QuadTree
...@@ -190,16 +190,16 @@ N_samples = 1000 ...@@ -190,16 +190,16 @@ N_samples = 1000
# Construct Tree # Construct Tree
boundary = SpaceTimeRectangle(-180, 180, -90, 90, datetime(2009, 1, 1, 0), datetime(2009, 1, 2, 23)) # Full domain boundary = SpaceTimeRectangle(-180, 180, -90, 90, datetime(2009, 1, 1, 0), datetime(2009, 1, 2, 23)) # Full domain
ot = OctTree(boundary) octtree = OctTree(boundary)
records: list[SpaceTimeRecord] = [ records: list[SpaceTimeRecord] = [
SpaceTimeRecord(choice(lon_range), choice(lat_range), choice(dates)) for _ in range(N_samples)] SpaceTimeRecord(choice(lon_range), choice(lat_range), choice(dates)) for _ in range(N_samples)]
for record in records: for record in records:
ot.insert(record) octtree.insert(record)
test_value: SpaceTimeRecord = SpaceTimeRecord(lon=47.6, lat=-31.1, datetime=datetime(2009, 1, 23, 17, 41)) test_value: SpaceTimeRecord = SpaceTimeRecord(lon=47.6, lat=-31.1, datetime=datetime(2009, 1, 23, 17, 41))
dist: float = 340 # km dist: float = 340 # km
t_dist = timedelta(hours=4) t_dist = timedelta(hours=4)
neighbours: list[Record] = ot.nearby_points(test_value, dist, t_dist) neighbours: list[Record] = octtree.nearby_points(test_value, dist, t_dist)
``` ```
No preview for this file type
%% Generated by Sphinx.
\def\sphinxdocclass{report}
\documentclass[letterpaper,10pt,english]{sphinxmanual}
\ifdefined\pdfpxdimen
\let\sphinxpxdimen\pdfpxdimen\else\newdimen\sphinxpxdimen
\fi \sphinxpxdimen=.75bp\relax
\ifdefined\pdfimageresolution
\pdfimageresolution= \numexpr \dimexpr1in\relax/\sphinxpxdimen\relax
\fi
%% let collapsible pdf bookmarks panel have high depth per default
\PassOptionsToPackage{bookmarksdepth=5}{hyperref}
\PassOptionsToPackage{booktabs}{sphinx}
\PassOptionsToPackage{colorrows}{sphinx}
\PassOptionsToPackage{warn}{textcomp}
\usepackage[utf8]{inputenc}
\ifdefined\DeclareUnicodeCharacter
% support both utf8 and utf8x syntaxes
\ifdefined\DeclareUnicodeCharacterAsOptional
\def\sphinxDUC#1{\DeclareUnicodeCharacter{"#1}}
\else
\let\sphinxDUC\DeclareUnicodeCharacter
\fi
\sphinxDUC{00A0}{\nobreakspace}
\sphinxDUC{2500}{\sphinxunichar{2500}}
\sphinxDUC{2502}{\sphinxunichar{2502}}
\sphinxDUC{2514}{\sphinxunichar{2514}}
\sphinxDUC{251C}{\sphinxunichar{251C}}
\sphinxDUC{2572}{\textbackslash}
\fi
\usepackage{cmap}
\usepackage[T1]{fontenc}
\usepackage{amsmath,amssymb,amstext}
\usepackage{babel}
\usepackage{tgtermes}
\usepackage{tgheros}
\renewcommand{\ttdefault}{txtt}
\usepackage[Bjarne]{fncychap}
\usepackage[,numfigreset=2,mathnumfig,mathnumsep={.}]{sphinx}
\fvset{fontsize=auto}
\usepackage{geometry}
% Include hyperref last.
\usepackage{hyperref}
% Fix anchor placement for figures with captions.
\usepackage{hypcap}% it must be loaded after hyperref.
% Set up styles of URL: it should be placed after hyperref.
\urlstyle{same}
\addto\captionsenglish{\renewcommand{\contentsname}{Contents:}}
\usepackage{sphinxmessages}
\setcounter{tocdepth}{3}
\setcounter{secnumdepth}{3}
\title{GeoSpatialTools}
\date{Feb 27, 2025}
\release{0.11.2}
\author{NOC Surface Processes}
\newcommand{\sphinxlogo}{\vbox{}}
\renewcommand{\releasename}{Release}
\makeindex
\begin{document}
\ifdefined\shorthandoff
\ifnum\catcode`\=\string=\active\shorthandoff{=}\fi
\ifnum\catcode`\"=\active\shorthandoff{"}\fi
\fi
\pagestyle{empty}
\sphinxmaketitle
\pagestyle{plain}
\sphinxtableofcontents
\pagestyle{normal}
\phantomsection\label{\detokenize{index::doc}}
\sphinxstepscope
\chapter{Introduction}
\label{\detokenize{introduction:introduction}}\label{\detokenize{introduction::doc}}
\sphinxAtStartPar
Python library containing useful functions and classes for Spatial Analysis.
\sphinxAtStartPar
Tested on Python versions 3.9 to 3.13.
\sphinxstepscope
\chapter{Getting Started}
\label{\detokenize{getting_started:getting-started}}\label{\detokenize{getting_started::doc}}
\section{Installation}
\label{\detokenize{getting_started:installation}}
\subsection{Via Pip}
\label{\detokenize{getting_started:via-pip}}
\sphinxAtStartPar
GeoSpatialTools is not available on PyPI, however it can be installed via pip with the following command:
\begin{sphinxVerbatim}[commandchars=\\\{\}]
\PYG{g+go}{pip install git+ssh://git@git.noc.ac.uk/nocsurfaceprocesses/geospatialtools.git}
\end{sphinxVerbatim}
\subsection{From Source}
\label{\detokenize{getting_started:from-source}}
\sphinxAtStartPar
Alternatively, you can clone the repository and install using pip (or conda if preferred).
\begin{sphinxVerbatim}[commandchars=\\\{\}]
\PYG{g+go}{git clone git@git.noc.ac.uk/nocsurfaceprocesses/geospatialtools.git}
\PYG{g+go}{cd geospatialtools}
\PYG{g+go}{python \PYGZhy{}m venv venv}
\PYG{g+go}{source venv/bin/activate}
\PYG{g+go}{pip install \PYGZhy{}e .}
\end{sphinxVerbatim}
\sphinxstepscope
\chapter{Credits}
\label{\detokenize{authors:credits}}\label{\detokenize{authors::doc}}
\section{Development Lead}
\label{\detokenize{authors:development-lead}}\begin{itemize}
\item {}
\sphinxAtStartPar
Joseph T. Siddons \textless{}\sphinxhref{mailto:josidd@noc.ac.uk}{josidd@noc.ac.uk}\textgreater{} \sphinxhref{git.noc.ac.uk/josidd}{@josidd}
\end{itemize}
\section{Contributoring Developers}
\label{\detokenize{authors:contributoring-developers}}\begin{itemize}
\item {}
\sphinxAtStartPar
Richard C. Cornes \textless{}\sphinxhref{mailto:rcornes@noc.ac.uk}{rcornes@noc.ac.uk}\textgreater{} \sphinxhref{git.noc.ac.uk/ricorne}{@ricorne}
\end{itemize}
\sphinxstepscope
\chapter{Users Guide}
\label{\detokenize{users_guide:module-GeoSpatialTools.neighbours}}\label{\detokenize{users_guide:users-guide}}\label{\detokenize{users_guide::doc}}\index{module@\spxentry{module}!GeoSpatialTools.neighbours@\spxentry{GeoSpatialTools.neighbours}}\index{GeoSpatialTools.neighbours@\spxentry{GeoSpatialTools.neighbours}!module@\spxentry{module}}
\section{Neighbours}
\label{\detokenize{users_guide:neighbours}}
\sphinxAtStartPar
Functions for finding nearest neighbours using bisection.
\index{SortedError@\spxentry{SortedError}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.neighbours.SortedError}}
\pysigstartsignatures
\pysigline
{\sphinxbfcode{\sphinxupquote{\DUrole{k}{exception}\DUrole{w}{ }}}\sphinxcode{\sphinxupquote{GeoSpatialTools.neighbours.}}\sphinxbfcode{\sphinxupquote{SortedError}}}
\pysigstopsignatures
\sphinxAtStartPar
Error class for Sortedness
\end{fulllineitems}
\index{SortedWarning@\spxentry{SortedWarning}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.neighbours.SortedWarning}}
\pysigstartsignatures
\pysigline
{\sphinxbfcode{\sphinxupquote{\DUrole{k}{exception}\DUrole{w}{ }}}\sphinxcode{\sphinxupquote{GeoSpatialTools.neighbours.}}\sphinxbfcode{\sphinxupquote{SortedWarning}}}
\pysigstopsignatures
\sphinxAtStartPar
Warning class for Sortedness
\end{fulllineitems}
\index{find\_nearest() (in module GeoSpatialTools.neighbours)@\spxentry{find\_nearest()}\spxextra{in module GeoSpatialTools.neighbours}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.neighbours.find_nearest}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxcode{\sphinxupquote{GeoSpatialTools.neighbours.}}\sphinxbfcode{\sphinxupquote{find\_nearest}}}
{\sphinxparam{\DUrole{n}{vals}}\sphinxparamcomma \sphinxparam{\DUrole{n}{test}}\sphinxparamcomma \sphinxparam{\DUrole{n}{check\_sorted}\DUrole{o}{=}\DUrole{default_value}{True}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Find the nearest value in a list of values for each test value.
\sphinxAtStartPar
Uses bisection for speediness!
\begin{quote}\begin{description}
\sphinxlineitem{Parameters}\begin{itemize}
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{vals}} (\sphinxstyleliteralemphasis{\sphinxupquote{list}}\sphinxstyleliteralemphasis{\sphinxupquote{{[}}}\sphinxstyleliteralemphasis{\sphinxupquote{Numeric}}\sphinxstyleliteralemphasis{\sphinxupquote{{]}}}) \textendash{} List of values \sphinxhyphen{} this is the pool of values for which we are looking
for a nearest match. This list MUST be sorted. Sortedness is not
checked, nor is the list sorted.
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{test}} (\sphinxstyleliteralemphasis{\sphinxupquote{list}}\sphinxstyleliteralemphasis{\sphinxupquote{{[}}}\sphinxstyleliteralemphasis{\sphinxupquote{Numeric}}\sphinxstyleliteralemphasis{\sphinxupquote{{]} }}\sphinxstyleliteralemphasis{\sphinxupquote{| }}\sphinxstyleliteralemphasis{\sphinxupquote{Numeric}}) \textendash{} List of query values
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{check\_sorted}} (\sphinxstyleliteralemphasis{\sphinxupquote{bool}}) \textendash{} Optionally check that the input vals is sorted. Raises an error if set
to True (default), displays a warning if set to False.
\end{itemize}
\sphinxlineitem{Return type}
\sphinxAtStartPar
\DUrole{sphinx_autodoc_typehints-type}{\sphinxcode{\sphinxupquote{Union}}{[}\sphinxcode{\sphinxupquote{List}}{[}\sphinxcode{\sphinxupquote{int}}{]}, \sphinxcode{\sphinxupquote{int}}{]}}
\sphinxlineitem{Returns}
\sphinxAtStartPar
\begin{itemize}
\item {}
\sphinxAtStartPar
\sphinxstyleemphasis{A list containing the index of the nearest neighbour in vals for each value}
\item {}
\sphinxAtStartPar
\sphinxstyleemphasis{in test. Or the index of the nearest neighbour if test is a single value.}
\end{itemize}
\end{description}\end{quote}
\end{fulllineitems}
\index{module@\spxentry{module}!GeoSpatialTools.quadtree@\spxentry{GeoSpatialTools.quadtree}}\index{GeoSpatialTools.quadtree@\spxentry{GeoSpatialTools.quadtree}!module@\spxentry{module}}
\section{QuadTree}
\label{\detokenize{users_guide:quadtree}}\label{\detokenize{users_guide:module-GeoSpatialTools.quadtree}}
\sphinxAtStartPar
Constuctors for QuadTree classes that can decrease the number of comparisons
for detecting nearby records for example. This is an implementation that uses
Haversine distances for comparisons between records for identification of
neighbours.
\index{Ellipse (class in GeoSpatialTools.quadtree)@\spxentry{Ellipse}\spxextra{class in GeoSpatialTools.quadtree}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.quadtree.Ellipse}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{\DUrole{k}{class}\DUrole{w}{ }}}\sphinxcode{\sphinxupquote{GeoSpatialTools.quadtree.}}\sphinxbfcode{\sphinxupquote{Ellipse}}}
{\sphinxparam{\DUrole{n}{lon}}\sphinxparamcomma \sphinxparam{\DUrole{n}{lat}}\sphinxparamcomma \sphinxparam{\DUrole{n}{a}}\sphinxparamcomma \sphinxparam{\DUrole{n}{b}}\sphinxparamcomma \sphinxparam{\DUrole{n}{theta}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
A simple Ellipse Class for an ellipse on the surface of a sphere.
\begin{quote}\begin{description}
\sphinxlineitem{Parameters}\begin{itemize}
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lon}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Horizontal centre of the ellipse
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lat}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Vertical centre of the ellipse
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{a}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Length of the semi\sphinxhyphen{}major axis
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{b}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Length of the semi\sphinxhyphen{}minor axis
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{theta}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Angle of the semi\sphinxhyphen{}major axis from horizontal anti\sphinxhyphen{}clockwise in radians
\end{itemize}
\end{description}\end{quote}
\index{contains() (GeoSpatialTools.quadtree.Ellipse method)@\spxentry{contains()}\spxextra{GeoSpatialTools.quadtree.Ellipse method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.quadtree.Ellipse.contains}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{contains}}}
{\sphinxparam{\DUrole{n}{point}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Test if a point is contained within the Ellipse
\begin{quote}\begin{description}
\sphinxlineitem{Return type}
\sphinxAtStartPar
\DUrole{sphinx_autodoc_typehints-type}{\sphinxcode{\sphinxupquote{bool}}}
\end{description}\end{quote}
\end{fulllineitems}
\index{nearby\_rect() (GeoSpatialTools.quadtree.Ellipse method)@\spxentry{nearby\_rect()}\spxextra{GeoSpatialTools.quadtree.Ellipse method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.quadtree.Ellipse.nearby_rect}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{nearby\_rect}}}
{\sphinxparam{\DUrole{n}{rect}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Test if a rectangle is near to the Ellipse
\begin{quote}\begin{description}
\sphinxlineitem{Return type}
\sphinxAtStartPar
\DUrole{sphinx_autodoc_typehints-type}{\sphinxcode{\sphinxupquote{bool}}}
\end{description}\end{quote}
\end{fulllineitems}
\end{fulllineitems}
\index{QuadTree (class in GeoSpatialTools.quadtree)@\spxentry{QuadTree}\spxextra{class in GeoSpatialTools.quadtree}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.quadtree.QuadTree}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{\DUrole{k}{class}\DUrole{w}{ }}}\sphinxcode{\sphinxupquote{GeoSpatialTools.quadtree.}}\sphinxbfcode{\sphinxupquote{QuadTree}}}
{\sphinxparam{\DUrole{n}{boundary}}\sphinxparamcomma \sphinxparam{\DUrole{n}{capacity}\DUrole{o}{=}\DUrole{default_value}{5}}\sphinxparamcomma \sphinxparam{\DUrole{n}{depth}\DUrole{o}{=}\DUrole{default_value}{0}}\sphinxparamcomma \sphinxparam{\DUrole{n}{max\_depth}\DUrole{o}{=}\DUrole{default_value}{None}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
A Simple QuadTree class for PyCOADS
\begin{quote}\begin{description}
\sphinxlineitem{Parameters}\begin{itemize}
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{boundary}} ({\hyperref[\detokenize{users_guide:GeoSpatialTools.quadtree.Rectangle}]{\sphinxcrossref{\sphinxstyleliteralemphasis{\sphinxupquote{Rectangle}}}}}) \textendash{} The bounding Rectangle of the QuadTree
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{capacity}} (\sphinxstyleliteralemphasis{\sphinxupquote{int}}) \textendash{} The capacity of each cell, if max\_depth is set then a cell at the
maximum depth may contain more points than the capacity.
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{depth}} (\sphinxstyleliteralemphasis{\sphinxupquote{int}}) \textendash{} The current depth of the cell. Initialises to zero if unset.
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{max\_depth}} (\sphinxstyleliteralemphasis{\sphinxupquote{int}}\sphinxstyleliteralemphasis{\sphinxupquote{ | }}\sphinxstyleliteralemphasis{\sphinxupquote{None}}) \textendash{} The maximum depth of the QuadTree. If set, this can override the
capacity for cells at the maximum depth.
\end{itemize}
\end{description}\end{quote}
\index{divide() (GeoSpatialTools.quadtree.QuadTree method)@\spxentry{divide()}\spxextra{GeoSpatialTools.quadtree.QuadTree method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.quadtree.QuadTree.divide}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{divide}}}
{}
{}
\pysigstopsignatures
\sphinxAtStartPar
Divide the QuadTree
\end{fulllineitems}
\index{insert() (GeoSpatialTools.quadtree.QuadTree method)@\spxentry{insert()}\spxextra{GeoSpatialTools.quadtree.QuadTree method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.quadtree.QuadTree.insert}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{insert}}}
{\sphinxparam{\DUrole{n}{point}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Insert a point into the QuadTree
\begin{quote}\begin{description}
\sphinxlineitem{Return type}
\sphinxAtStartPar
\DUrole{sphinx_autodoc_typehints-type}{\sphinxcode{\sphinxupquote{bool}}}
\end{description}\end{quote}
\end{fulllineitems}
\index{len() (GeoSpatialTools.quadtree.QuadTree method)@\spxentry{len()}\spxextra{GeoSpatialTools.quadtree.QuadTree method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.quadtree.QuadTree.len}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{len}}}
{\sphinxparam{\DUrole{n}{\_current\_len}\DUrole{o}{=}\DUrole{default_value}{0}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Get the number of points in the OctTree
\begin{quote}\begin{description}
\sphinxlineitem{Return type}
\sphinxAtStartPar
\DUrole{sphinx_autodoc_typehints-type}{\sphinxcode{\sphinxupquote{int}}}
\end{description}\end{quote}
\end{fulllineitems}
\index{nearby\_points() (GeoSpatialTools.quadtree.QuadTree method)@\spxentry{nearby\_points()}\spxextra{GeoSpatialTools.quadtree.QuadTree method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.quadtree.QuadTree.nearby_points}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{nearby\_points}}}
{\sphinxparam{\DUrole{n}{point}}\sphinxparamcomma \sphinxparam{\DUrole{n}{dist}}\sphinxparamcomma \sphinxparam{\DUrole{n}{points}\DUrole{o}{=}\DUrole{default_value}{None}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Get all points that are nearby another point
\begin{quote}\begin{description}
\sphinxlineitem{Return type}
\sphinxAtStartPar
\DUrole{sphinx_autodoc_typehints-type}{\sphinxcode{\sphinxupquote{List}}{[}{\hyperref[\detokenize{users_guide:GeoSpatialTools.quadtree.Record}]{\sphinxcrossref{\sphinxcode{\sphinxupquote{Record}}}}}{]}}
\end{description}\end{quote}
\end{fulllineitems}
\index{query() (GeoSpatialTools.quadtree.QuadTree method)@\spxentry{query()}\spxextra{GeoSpatialTools.quadtree.QuadTree method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.quadtree.QuadTree.query}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{query}}}
{\sphinxparam{\DUrole{n}{rect}}\sphinxparamcomma \sphinxparam{\DUrole{n}{points}\DUrole{o}{=}\DUrole{default_value}{None}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Get points that fall in a rectangle
\begin{quote}\begin{description}
\sphinxlineitem{Return type}
\sphinxAtStartPar
\DUrole{sphinx_autodoc_typehints-type}{\sphinxcode{\sphinxupquote{List}}{[}{\hyperref[\detokenize{users_guide:GeoSpatialTools.quadtree.Record}]{\sphinxcrossref{\sphinxcode{\sphinxupquote{Record}}}}}{]}}
\end{description}\end{quote}
\end{fulllineitems}
\index{query\_ellipse() (GeoSpatialTools.quadtree.QuadTree method)@\spxentry{query\_ellipse()}\spxextra{GeoSpatialTools.quadtree.QuadTree method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.quadtree.QuadTree.query_ellipse}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{query\_ellipse}}}
{\sphinxparam{\DUrole{n}{ellipse}}\sphinxparamcomma \sphinxparam{\DUrole{n}{points}\DUrole{o}{=}\DUrole{default_value}{None}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Get points that fall in an ellipse.
\begin{quote}\begin{description}
\sphinxlineitem{Return type}
\sphinxAtStartPar
\DUrole{sphinx_autodoc_typehints-type}{\sphinxcode{\sphinxupquote{List}}{[}{\hyperref[\detokenize{users_guide:GeoSpatialTools.quadtree.Record}]{\sphinxcrossref{\sphinxcode{\sphinxupquote{Record}}}}}{]}}
\end{description}\end{quote}
\end{fulllineitems}
\index{remove() (GeoSpatialTools.quadtree.QuadTree method)@\spxentry{remove()}\spxextra{GeoSpatialTools.quadtree.QuadTree method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.quadtree.QuadTree.remove}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{remove}}}
{\sphinxparam{\DUrole{n}{point}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Remove a Record from the QuadTree if it is in the QuadTree.
\sphinxAtStartPar
Returns True if the Record is removed.
\begin{quote}\begin{description}
\sphinxlineitem{Return type}
\sphinxAtStartPar
\DUrole{sphinx_autodoc_typehints-type}{\sphinxcode{\sphinxupquote{bool}}}
\end{description}\end{quote}
\end{fulllineitems}
\end{fulllineitems}
\index{Record (class in GeoSpatialTools.quadtree)@\spxentry{Record}\spxextra{class in GeoSpatialTools.quadtree}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.quadtree.Record}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{\DUrole{k}{class}\DUrole{w}{ }}}\sphinxcode{\sphinxupquote{GeoSpatialTools.quadtree.}}\sphinxbfcode{\sphinxupquote{Record}}}
{\sphinxparam{\DUrole{n}{lon}}\sphinxparamcomma \sphinxparam{\DUrole{n}{lat}}\sphinxparamcomma \sphinxparam{\DUrole{n}{datetime}\DUrole{o}{=}\DUrole{default_value}{None}}\sphinxparamcomma \sphinxparam{\DUrole{n}{uid}\DUrole{o}{=}\DUrole{default_value}{None}}\sphinxparamcomma \sphinxparam{\DUrole{n}{fix\_lon}\DUrole{o}{=}\DUrole{default_value}{True}}\sphinxparamcomma \sphinxparam{\DUrole{o}{**}\DUrole{n}{data}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
ICOADS Record class
\sphinxAtStartPar
This is a simple instance of an ICOARDS record, it requires position data.
It can optionally include datetime, a UID, and extra data passed as
keyword arguments.
\sphinxAtStartPar
Equality is checked only on the required fields + UID if it is specified.
\begin{quote}\begin{description}
\sphinxlineitem{Parameters}\begin{itemize}
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lon}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Horizontal coordinate
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lat}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Vertical coordinate
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{datetime}} (\sphinxstyleliteralemphasis{\sphinxupquote{datetime}}\sphinxstyleliteralemphasis{\sphinxupquote{ | }}\sphinxstyleliteralemphasis{\sphinxupquote{None}}) \textendash{} Datetime of the record
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{uid}} (\sphinxstyleliteralemphasis{\sphinxupquote{str}}\sphinxstyleliteralemphasis{\sphinxupquote{ | }}\sphinxstyleliteralemphasis{\sphinxupquote{None}}) \textendash{} Unique Identifier
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{fix\_lon}} (\sphinxstyleliteralemphasis{\sphinxupquote{bool}}) \textendash{} Force longitude to \sphinxhyphen{}180, 180
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{**data}} \textendash{} Additional data passed to the Record for use by other functions or
classes.
\end{itemize}
\end{description}\end{quote}
\index{distance() (GeoSpatialTools.quadtree.Record method)@\spxentry{distance()}\spxextra{GeoSpatialTools.quadtree.Record method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.quadtree.Record.distance}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{distance}}}
{\sphinxparam{\DUrole{n}{other}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Compute the Haversine distance to another Record
\begin{quote}\begin{description}
\sphinxlineitem{Return type}
\sphinxAtStartPar
\DUrole{sphinx_autodoc_typehints-type}{\sphinxcode{\sphinxupquote{float}}}
\end{description}\end{quote}
\end{fulllineitems}
\end{fulllineitems}
\index{Rectangle (class in GeoSpatialTools.quadtree)@\spxentry{Rectangle}\spxextra{class in GeoSpatialTools.quadtree}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.quadtree.Rectangle}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{\DUrole{k}{class}\DUrole{w}{ }}}\sphinxcode{\sphinxupquote{GeoSpatialTools.quadtree.}}\sphinxbfcode{\sphinxupquote{Rectangle}}}
{\sphinxparam{\DUrole{n}{west}}\sphinxparamcomma \sphinxparam{\DUrole{n}{east}}\sphinxparamcomma \sphinxparam{\DUrole{n}{south}}\sphinxparamcomma \sphinxparam{\DUrole{n}{north}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
A simple Rectangle class
\begin{quote}\begin{description}
\sphinxlineitem{Parameters}\begin{itemize}
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{west}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Western boundary of the Rectangle
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{east}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Eastern boundary of the Rectangle
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{south}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Southern boundary of the Rectangle
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{north}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Northern boundary of the Rectangle
\end{itemize}
\end{description}\end{quote}
\index{contains() (GeoSpatialTools.quadtree.Rectangle method)@\spxentry{contains()}\spxextra{GeoSpatialTools.quadtree.Rectangle method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.quadtree.Rectangle.contains}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{contains}}}
{\sphinxparam{\DUrole{n}{point}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Test if a point is contained within the Rectangle
\begin{quote}\begin{description}
\sphinxlineitem{Return type}
\sphinxAtStartPar
\DUrole{sphinx_autodoc_typehints-type}{\sphinxcode{\sphinxupquote{bool}}}
\end{description}\end{quote}
\end{fulllineitems}
\index{edge\_dist (GeoSpatialTools.quadtree.Rectangle property)@\spxentry{edge\_dist}\spxextra{GeoSpatialTools.quadtree.Rectangle property}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.quadtree.Rectangle.edge_dist}}
\pysigstartsignatures
\pysigline
{\sphinxbfcode{\sphinxupquote{\DUrole{k}{property}\DUrole{w}{ }}}\sphinxbfcode{\sphinxupquote{edge\_dist}}\sphinxbfcode{\sphinxupquote{\DUrole{p}{:}\DUrole{w}{ }float}}}
\pysigstopsignatures
\sphinxAtStartPar
Approximate maximum distance from the centre to an edge
\end{fulllineitems}
\index{intersects() (GeoSpatialTools.quadtree.Rectangle method)@\spxentry{intersects()}\spxextra{GeoSpatialTools.quadtree.Rectangle method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.quadtree.Rectangle.intersects}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{intersects}}}
{\sphinxparam{\DUrole{n}{other}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Test if another Rectangle object intersects this Rectangle
\begin{quote}\begin{description}
\sphinxlineitem{Return type}
\sphinxAtStartPar
\DUrole{sphinx_autodoc_typehints-type}{\sphinxcode{\sphinxupquote{bool}}}
\end{description}\end{quote}
\end{fulllineitems}
\index{lat (GeoSpatialTools.quadtree.Rectangle property)@\spxentry{lat}\spxextra{GeoSpatialTools.quadtree.Rectangle property}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.quadtree.Rectangle.lat}}
\pysigstartsignatures
\pysigline
{\sphinxbfcode{\sphinxupquote{\DUrole{k}{property}\DUrole{w}{ }}}\sphinxbfcode{\sphinxupquote{lat}}\sphinxbfcode{\sphinxupquote{\DUrole{p}{:}\DUrole{w}{ }float}}}
\pysigstopsignatures
\sphinxAtStartPar
Centre latitude of the Rectangle
\end{fulllineitems}
\index{lat\_range (GeoSpatialTools.quadtree.Rectangle property)@\spxentry{lat\_range}\spxextra{GeoSpatialTools.quadtree.Rectangle property}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.quadtree.Rectangle.lat_range}}
\pysigstartsignatures
\pysigline
{\sphinxbfcode{\sphinxupquote{\DUrole{k}{property}\DUrole{w}{ }}}\sphinxbfcode{\sphinxupquote{lat\_range}}\sphinxbfcode{\sphinxupquote{\DUrole{p}{:}\DUrole{w}{ }float}}}
\pysigstopsignatures
\sphinxAtStartPar
Latitude range of the Rectangle
\end{fulllineitems}
\index{lon (GeoSpatialTools.quadtree.Rectangle property)@\spxentry{lon}\spxextra{GeoSpatialTools.quadtree.Rectangle property}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.quadtree.Rectangle.lon}}
\pysigstartsignatures
\pysigline
{\sphinxbfcode{\sphinxupquote{\DUrole{k}{property}\DUrole{w}{ }}}\sphinxbfcode{\sphinxupquote{lon}}\sphinxbfcode{\sphinxupquote{\DUrole{p}{:}\DUrole{w}{ }float}}}
\pysigstopsignatures
\sphinxAtStartPar
Centre longitude of the Rectangle
\end{fulllineitems}
\index{lon\_range (GeoSpatialTools.quadtree.Rectangle property)@\spxentry{lon\_range}\spxextra{GeoSpatialTools.quadtree.Rectangle property}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.quadtree.Rectangle.lon_range}}
\pysigstartsignatures
\pysigline
{\sphinxbfcode{\sphinxupquote{\DUrole{k}{property}\DUrole{w}{ }}}\sphinxbfcode{\sphinxupquote{lon\_range}}\sphinxbfcode{\sphinxupquote{\DUrole{p}{:}\DUrole{w}{ }float}}}
\pysigstopsignatures
\sphinxAtStartPar
Longitude range of the Rectangle
\end{fulllineitems}
\index{nearby() (GeoSpatialTools.quadtree.Rectangle method)@\spxentry{nearby()}\spxextra{GeoSpatialTools.quadtree.Rectangle method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.quadtree.Rectangle.nearby}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{nearby}}}
{\sphinxparam{\DUrole{n}{point}}\sphinxparamcomma \sphinxparam{\DUrole{n}{dist}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Check if point is nearby the Rectangle
\begin{quote}\begin{description}
\sphinxlineitem{Return type}
\sphinxAtStartPar
\DUrole{sphinx_autodoc_typehints-type}{\sphinxcode{\sphinxupquote{bool}}}
\end{description}\end{quote}
\end{fulllineitems}
\end{fulllineitems}
\index{module@\spxentry{module}!GeoSpatialTools.octtree@\spxentry{GeoSpatialTools.octtree}}\index{GeoSpatialTools.octtree@\spxentry{GeoSpatialTools.octtree}!module@\spxentry{module}}
\section{OctTree}
\label{\detokenize{users_guide:octtree}}\label{\detokenize{users_guide:module-GeoSpatialTools.octtree}}
\sphinxAtStartPar
Constuctors for OctTree classes that can decrease the number of comparisons
for detecting nearby records for example. This is an implementation that uses
Haversine distances for comparisons between records for identification of
neighbours.
\index{OctTree (class in GeoSpatialTools.octtree)@\spxentry{OctTree}\spxextra{class in GeoSpatialTools.octtree}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.octtree.OctTree}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{\DUrole{k}{class}\DUrole{w}{ }}}\sphinxcode{\sphinxupquote{GeoSpatialTools.octtree.}}\sphinxbfcode{\sphinxupquote{OctTree}}}
{\sphinxparam{\DUrole{n}{boundary}}\sphinxparamcomma \sphinxparam{\DUrole{n}{capacity}\DUrole{o}{=}\DUrole{default_value}{5}}\sphinxparamcomma \sphinxparam{\DUrole{n}{depth}\DUrole{o}{=}\DUrole{default_value}{0}}\sphinxparamcomma \sphinxparam{\DUrole{n}{max\_depth}\DUrole{o}{=}\DUrole{default_value}{None}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
A Simple OctTree class for PyCOADS.
\sphinxAtStartPar
Acts as a space\sphinxhyphen{}time OctTree on the surface of Earth, allowing for querying
nearby points faster than searching a full DataFrame. As SpaceTimeRecords
are added to the OctTree, the OctTree divides into 8 children as the
capacity is reached. Additional SpaceTimeRecords are then added to the
children where they fall within the child OctTree’s boundary.
\sphinxAtStartPar
SpaceTimeRecords already part of the OctTree before divided are not
distributed to the children OctTrees.
\sphinxAtStartPar
Whilst the OctTree has a temporal component, and was designed to utilise
datetime / timedelta objects, numeric values and ranges can be used. This
usage must be consistent for the boundary and all SpaceTimeRecords that
are part of the OctTree. This allows for usage of pentad, timestamp,
Julian day, etc. as datetime values.
\begin{quote}\begin{description}
\sphinxlineitem{Parameters}\begin{itemize}
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{boundary}} ({\hyperref[\detokenize{users_guide:GeoSpatialTools.octtree.SpaceTimeRectangle}]{\sphinxcrossref{\sphinxstyleliteralemphasis{\sphinxupquote{SpaceTimeRectangle}}}}}) \textendash{} The bounding SpaceTimeRectangle of the QuadTree
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{capacity}} (\sphinxstyleliteralemphasis{\sphinxupquote{int}}) \textendash{} The capacity of each cell, if max\_depth is set then a cell at the
maximum depth may contain more points than the capacity.
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{depth}} (\sphinxstyleliteralemphasis{\sphinxupquote{int}}) \textendash{} The current depth of the cell. Initialises to zero if unset.
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{max\_depth}} (\sphinxstyleliteralemphasis{\sphinxupquote{int}}\sphinxstyleliteralemphasis{\sphinxupquote{ | }}\sphinxstyleliteralemphasis{\sphinxupquote{None}}) \textendash{} The maximum depth of the QuadTree. If set, this can override the
capacity for cells at the maximum depth.
\end{itemize}
\end{description}\end{quote}
\index{divide() (GeoSpatialTools.octtree.OctTree method)@\spxentry{divide()}\spxextra{GeoSpatialTools.octtree.OctTree method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.octtree.OctTree.divide}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{divide}}}
{}
{}
\pysigstopsignatures
\sphinxAtStartPar
Divide the QuadTree
\end{fulllineitems}
\index{insert() (GeoSpatialTools.octtree.OctTree method)@\spxentry{insert()}\spxextra{GeoSpatialTools.octtree.OctTree method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.octtree.OctTree.insert}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{insert}}}
{\sphinxparam{\DUrole{n}{point}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Insert a SpaceTimeRecord into the QuadTree.
\sphinxAtStartPar
Note that the SpaceTimeRecord can have numeric datetime values if that
is consistent with the OctTree.
\begin{quote}\begin{description}
\sphinxlineitem{Return type}
\sphinxAtStartPar
\DUrole{sphinx_autodoc_typehints-type}{\sphinxcode{\sphinxupquote{bool}}}
\end{description}\end{quote}
\end{fulllineitems}
\index{len() (GeoSpatialTools.octtree.OctTree method)@\spxentry{len()}\spxextra{GeoSpatialTools.octtree.OctTree method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.octtree.OctTree.len}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{len}}}
{\sphinxparam{\DUrole{n}{\_current\_len}\DUrole{o}{=}\DUrole{default_value}{0}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Get the number of points in the OctTree
\begin{quote}\begin{description}
\sphinxlineitem{Return type}
\sphinxAtStartPar
\DUrole{sphinx_autodoc_typehints-type}{\sphinxcode{\sphinxupquote{int}}}
\end{description}\end{quote}
\end{fulllineitems}
\index{nearby\_points() (GeoSpatialTools.octtree.OctTree method)@\spxentry{nearby\_points()}\spxextra{GeoSpatialTools.octtree.OctTree method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.octtree.OctTree.nearby_points}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{nearby\_points}}}
{\sphinxparam{\DUrole{n}{point}}\sphinxparamcomma \sphinxparam{\DUrole{n}{dist}}\sphinxparamcomma \sphinxparam{\DUrole{n}{t\_dist}}\sphinxparamcomma \sphinxparam{\DUrole{n}{points}\DUrole{o}{=}\DUrole{default_value}{None}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Get all points that are nearby another point.
\sphinxAtStartPar
Query the OctTree to find all SpaceTimeRecords within the OctTree that
are nearby to the query SpaceTimeRecord. This search should be faster
than searching through all records, since only OctTree children whose
boundaries are close to the query SpaceTimeRecord are evaluated.
\begin{quote}\begin{description}
\sphinxlineitem{Parameters}\begin{itemize}
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{point}} ({\hyperref[\detokenize{users_guide:GeoSpatialTools.octtree.SpaceTimeRecord}]{\sphinxcrossref{\sphinxstyleliteralemphasis{\sphinxupquote{SpaceTimeRecord}}}}}) \textendash{} The query point.
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{dist}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} The distance for comparison. Note that Haversine distance is used
as the distance metric as the query SpaceTimeRecord and OctTree are
assumed to lie on the surface of Earth.
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{t\_dist}} (\sphinxstyleliteralemphasis{\sphinxupquote{datetime.timedelta}}) \textendash{} Max time gap between SpaceTimeRecords within the OctTree and the
query SpaceTimeRecord. Can be numeric if the OctTree boundaries,
SpaceTimeRecords, and query SpaceTimeRecord have numeric datetime
values and ranges.
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{points}} ({\hyperref[\detokenize{users_guide:GeoSpatialTools.octtree.SpaceTimeRecords}]{\sphinxcrossref{\sphinxstyleliteralemphasis{\sphinxupquote{SpaceTimeRecords}}}}}\sphinxstyleliteralemphasis{\sphinxupquote{ | }}\sphinxstyleliteralemphasis{\sphinxupquote{None}}) \textendash{} List of SpaceTimeRecords already found. Most use cases will be to
not set this value, since it’s main use is for passing onto the
children OctTrees.
\end{itemize}
\sphinxlineitem{Return type}
\sphinxAtStartPar
\DUrole{sphinx_autodoc_typehints-type}{{\hyperref[\detokenize{users_guide:GeoSpatialTools.octtree.SpaceTimeRecords}]{\sphinxcrossref{\sphinxcode{\sphinxupquote{SpaceTimeRecords}}}}}}
\sphinxlineitem{Returns}
\sphinxAtStartPar
\begin{itemize}
\item {}
\sphinxAtStartPar
\sphinxstylestrong{SpaceTimeRecords} (\sphinxstyleemphasis{A list of SpaceTimeRecords whose distance to the})
\item {}
\sphinxAtStartPar
\sphinxstyleemphasis{query SpaceTimeRecord is \textless{}= dist, and the datetimes of the}
\item {}
\sphinxAtStartPar
\sphinxstyleemphasis{SpaceTimeRecords fall within the datetime range of the query}
\item {}
\sphinxAtStartPar
\sphinxstyleemphasis{SpaceTimeRecord.}
\end{itemize}
\end{description}\end{quote}
\end{fulllineitems}
\index{query() (GeoSpatialTools.octtree.OctTree method)@\spxentry{query()}\spxextra{GeoSpatialTools.octtree.OctTree method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.octtree.OctTree.query}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{query}}}
{\sphinxparam{\DUrole{n}{rect}}\sphinxparamcomma \sphinxparam{\DUrole{n}{points}\DUrole{o}{=}\DUrole{default_value}{None}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Get points that fall in a SpaceTimeRectangle
\begin{quote}\begin{description}
\sphinxlineitem{Return type}
\sphinxAtStartPar
\DUrole{sphinx_autodoc_typehints-type}{{\hyperref[\detokenize{users_guide:GeoSpatialTools.octtree.SpaceTimeRecords}]{\sphinxcrossref{\sphinxcode{\sphinxupquote{SpaceTimeRecords}}}}}}
\end{description}\end{quote}
\end{fulllineitems}
\index{query\_ellipse() (GeoSpatialTools.octtree.OctTree method)@\spxentry{query\_ellipse()}\spxextra{GeoSpatialTools.octtree.OctTree method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.octtree.OctTree.query_ellipse}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{query\_ellipse}}}
{\sphinxparam{\DUrole{n}{ellipse}}\sphinxparamcomma \sphinxparam{\DUrole{n}{points}\DUrole{o}{=}\DUrole{default_value}{None}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Get points that fall in an ellipse.
\begin{quote}\begin{description}
\sphinxlineitem{Return type}
\sphinxAtStartPar
\DUrole{sphinx_autodoc_typehints-type}{{\hyperref[\detokenize{users_guide:GeoSpatialTools.octtree.SpaceTimeRecords}]{\sphinxcrossref{\sphinxcode{\sphinxupquote{SpaceTimeRecords}}}}}}
\end{description}\end{quote}
\end{fulllineitems}
\index{remove() (GeoSpatialTools.octtree.OctTree method)@\spxentry{remove()}\spxextra{GeoSpatialTools.octtree.OctTree method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.octtree.OctTree.remove}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{remove}}}
{\sphinxparam{\DUrole{n}{point}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Remove a SpaceTimeRecord from the OctTree if it is in the OctTree.
\sphinxAtStartPar
Returns True if the SpaceTimeRecord is removed.
\begin{quote}\begin{description}
\sphinxlineitem{Return type}
\sphinxAtStartPar
\DUrole{sphinx_autodoc_typehints-type}{\sphinxcode{\sphinxupquote{bool}}}
\end{description}\end{quote}
\end{fulllineitems}
\end{fulllineitems}
\index{SpaceTimeEllipse (class in GeoSpatialTools.octtree)@\spxentry{SpaceTimeEllipse}\spxextra{class in GeoSpatialTools.octtree}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.octtree.SpaceTimeEllipse}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{\DUrole{k}{class}\DUrole{w}{ }}}\sphinxcode{\sphinxupquote{GeoSpatialTools.octtree.}}\sphinxbfcode{\sphinxupquote{SpaceTimeEllipse}}}
{\sphinxparam{\DUrole{n}{lon}}\sphinxparamcomma \sphinxparam{\DUrole{n}{lat}}\sphinxparamcomma \sphinxparam{\DUrole{n}{a}}\sphinxparamcomma \sphinxparam{\DUrole{n}{b}}\sphinxparamcomma \sphinxparam{\DUrole{n}{theta}}\sphinxparamcomma \sphinxparam{\DUrole{n}{start}}\sphinxparamcomma \sphinxparam{\DUrole{n}{end}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
A simple Ellipse Class for an ellipse on the surface of a sphere.
\begin{quote}\begin{description}
\sphinxlineitem{Parameters}\begin{itemize}
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lon}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Horizontal centre of the ellipse
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lat}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Vertical centre of the ellipse
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{a}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Length of the semi\sphinxhyphen{}major axis
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{b}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Length of the semi\sphinxhyphen{}minor axis
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{theta}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Angle of the semi\sphinxhyphen{}major axis from horizontal anti\sphinxhyphen{}clockwise in radians
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{start}} (\sphinxstyleliteralemphasis{\sphinxupquote{datetime.datetime}}) \textendash{} Start date of the Ellipse
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{end}} (\sphinxstyleliteralemphasis{\sphinxupquote{datetime.datetime}}) \textendash{} Send date of the Ellipse
\end{itemize}
\end{description}\end{quote}
\index{contains() (GeoSpatialTools.octtree.SpaceTimeEllipse method)@\spxentry{contains()}\spxextra{GeoSpatialTools.octtree.SpaceTimeEllipse method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.octtree.SpaceTimeEllipse.contains}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{contains}}}
{\sphinxparam{\DUrole{n}{point}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Test if a point is contained within the Ellipse
\begin{quote}\begin{description}
\sphinxlineitem{Return type}
\sphinxAtStartPar
\DUrole{sphinx_autodoc_typehints-type}{\sphinxcode{\sphinxupquote{bool}}}
\end{description}\end{quote}
\end{fulllineitems}
\index{nearby\_rect() (GeoSpatialTools.octtree.SpaceTimeEllipse method)@\spxentry{nearby\_rect()}\spxextra{GeoSpatialTools.octtree.SpaceTimeEllipse method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.octtree.SpaceTimeEllipse.nearby_rect}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{nearby\_rect}}}
{\sphinxparam{\DUrole{n}{rect}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Test if a rectangle is near to the Ellipse
\begin{quote}\begin{description}
\sphinxlineitem{Return type}
\sphinxAtStartPar
\DUrole{sphinx_autodoc_typehints-type}{\sphinxcode{\sphinxupquote{bool}}}
\end{description}\end{quote}
\end{fulllineitems}
\end{fulllineitems}
\index{SpaceTimeRecord (class in GeoSpatialTools.octtree)@\spxentry{SpaceTimeRecord}\spxextra{class in GeoSpatialTools.octtree}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.octtree.SpaceTimeRecord}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{\DUrole{k}{class}\DUrole{w}{ }}}\sphinxcode{\sphinxupquote{GeoSpatialTools.octtree.}}\sphinxbfcode{\sphinxupquote{SpaceTimeRecord}}}
{\sphinxparam{\DUrole{n}{lon}}\sphinxparamcomma \sphinxparam{\DUrole{n}{lat}}\sphinxparamcomma \sphinxparam{\DUrole{n}{datetime}}\sphinxparamcomma \sphinxparam{\DUrole{n}{uid}\DUrole{o}{=}\DUrole{default_value}{None}}\sphinxparamcomma \sphinxparam{\DUrole{n}{fix\_lon}\DUrole{o}{=}\DUrole{default_value}{True}}\sphinxparamcomma \sphinxparam{\DUrole{o}{**}\DUrole{n}{data}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
ICOADS Record class.
\sphinxAtStartPar
This is a simple instance of an ICOARDS record, it requires position and
temporal data. It can optionally include a UID and extra data.
\sphinxAtStartPar
The temporal component was designed to use \sphinxtitleref{datetime} values, however all
methods will work with numeric datetime information \sphinxhyphen{} for example a pentad,
timestamp, julian day, etc. Note that any uses within an OctTree and
SpaceTimeRectangle must also have timedelta values replaced with numeric
ranges in this case.
\sphinxAtStartPar
Equality is checked only on the required fields + UID if it is specified.
\begin{quote}\begin{description}
\sphinxlineitem{Parameters}\begin{itemize}
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lon}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Horizontal coordinate (longitude).
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lat}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Vertical coordinate (latitude).
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{datetime}} (\sphinxstyleliteralemphasis{\sphinxupquote{datetime.datetime}}) \textendash{} Datetime of the record. Can also be a numeric value such as pentad.
Comparisons between Records with datetime and Records with numeric
datetime will fail.
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{uid}} (\sphinxstyleliteralemphasis{\sphinxupquote{str}}\sphinxstyleliteralemphasis{\sphinxupquote{ | }}\sphinxstyleliteralemphasis{\sphinxupquote{None}}) \textendash{} Unique Identifier.
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{fix\_lon}} (\sphinxstyleliteralemphasis{\sphinxupquote{bool}}) \textendash{} Force longitude to \sphinxhyphen{}180, 180
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{**data}} \textendash{} Additional data passed to the SpaceTimeRecord for use by other functions
or classes.
\end{itemize}
\end{description}\end{quote}
\index{distance() (GeoSpatialTools.octtree.SpaceTimeRecord method)@\spxentry{distance()}\spxextra{GeoSpatialTools.octtree.SpaceTimeRecord method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.octtree.SpaceTimeRecord.distance}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{distance}}}
{\sphinxparam{\DUrole{n}{other}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Compute the Haversine distance to another SpaceTimeRecord.
Only computes spatial distance.
\begin{quote}\begin{description}
\sphinxlineitem{Return type}
\sphinxAtStartPar
\DUrole{sphinx_autodoc_typehints-type}{\sphinxcode{\sphinxupquote{float}}}
\end{description}\end{quote}
\end{fulllineitems}
\end{fulllineitems}
\index{SpaceTimeRecords (class in GeoSpatialTools.octtree)@\spxentry{SpaceTimeRecords}\spxextra{class in GeoSpatialTools.octtree}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.octtree.SpaceTimeRecords}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{\DUrole{k}{class}\DUrole{w}{ }}}\sphinxcode{\sphinxupquote{GeoSpatialTools.octtree.}}\sphinxbfcode{\sphinxupquote{SpaceTimeRecords}}}
{\sphinxparam{\DUrole{n}{iterable}\DUrole{o}{=}\DUrole{default_value}{()}}\sphinxparamcomma \sphinxparam{\DUrole{positional-only-separator}{\DUrole{o}{\sphinxstyleabbreviation{/} (Positional\sphinxhyphen{}only parameter separator (PEP 570))}}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
List of SpaceTimeRecords
\end{fulllineitems}
\index{SpaceTimeRectangle (class in GeoSpatialTools.octtree)@\spxentry{SpaceTimeRectangle}\spxextra{class in GeoSpatialTools.octtree}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.octtree.SpaceTimeRectangle}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{\DUrole{k}{class}\DUrole{w}{ }}}\sphinxcode{\sphinxupquote{GeoSpatialTools.octtree.}}\sphinxbfcode{\sphinxupquote{SpaceTimeRectangle}}}
{\sphinxparam{\DUrole{n}{west}}\sphinxparamcomma \sphinxparam{\DUrole{n}{east}}\sphinxparamcomma \sphinxparam{\DUrole{n}{south}}\sphinxparamcomma \sphinxparam{\DUrole{n}{north}}\sphinxparamcomma \sphinxparam{\DUrole{n}{start}}\sphinxparamcomma \sphinxparam{\DUrole{n}{end}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
A simple Space Time SpaceTimeRectangle class.
\sphinxAtStartPar
This constructs a simple Rectangle object.
The defining coordinates are the centres of the box, and the extents
are the full width, height, and time extent.
\sphinxAtStartPar
Whilst the rectangle is assumed to lie on the surface of Earth, this is
a projection as the rectangle is defined by a longitude/latitude range.
\sphinxAtStartPar
The temporal components are defined in the same way as the spatial
components, that is that the \sphinxtitleref{datetime} component (t) is the “centre”, and
the time extent (dt) is the full time range of the box.
\begin{quote}\begin{description}
\sphinxlineitem{Parameters}\begin{itemize}
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{west}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Western boundary of the Rectangle
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{east}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Eastern boundary of the Rectangle
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{south}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Southern boundary of the Rectangle
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{north}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Northern boundary of the Rectangle
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{start}} (\sphinxstyleliteralemphasis{\sphinxupquote{datetime.datetime}}) \textendash{} Start datetime of the Rectangle
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{end}} (\sphinxstyleliteralemphasis{\sphinxupquote{datetime.datetime}}) \textendash{} End datetime of the Rectangle
\end{itemize}
\end{description}\end{quote}
\index{centre\_datetime (GeoSpatialTools.octtree.SpaceTimeRectangle property)@\spxentry{centre\_datetime}\spxextra{GeoSpatialTools.octtree.SpaceTimeRectangle property}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.octtree.SpaceTimeRectangle.centre_datetime}}
\pysigstartsignatures
\pysigline
{\sphinxbfcode{\sphinxupquote{\DUrole{k}{property}\DUrole{w}{ }}}\sphinxbfcode{\sphinxupquote{centre\_datetime}}\sphinxbfcode{\sphinxupquote{\DUrole{p}{:}\DUrole{w}{ }datetime}}}
\pysigstopsignatures
\sphinxAtStartPar
The midpoint time of the Rectangle
\end{fulllineitems}
\index{contains() (GeoSpatialTools.octtree.SpaceTimeRectangle method)@\spxentry{contains()}\spxextra{GeoSpatialTools.octtree.SpaceTimeRectangle method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.octtree.SpaceTimeRectangle.contains}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{contains}}}
{\sphinxparam{\DUrole{n}{point}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Test if a point is contained within the SpaceTimeRectangle
\begin{quote}\begin{description}
\sphinxlineitem{Return type}
\sphinxAtStartPar
\DUrole{sphinx_autodoc_typehints-type}{\sphinxcode{\sphinxupquote{bool}}}
\end{description}\end{quote}
\end{fulllineitems}
\index{edge\_dist (GeoSpatialTools.octtree.SpaceTimeRectangle property)@\spxentry{edge\_dist}\spxextra{GeoSpatialTools.octtree.SpaceTimeRectangle property}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.octtree.SpaceTimeRectangle.edge_dist}}
\pysigstartsignatures
\pysigline
{\sphinxbfcode{\sphinxupquote{\DUrole{k}{property}\DUrole{w}{ }}}\sphinxbfcode{\sphinxupquote{edge\_dist}}\sphinxbfcode{\sphinxupquote{\DUrole{p}{:}\DUrole{w}{ }float}}}
\pysigstopsignatures
\sphinxAtStartPar
Approximate maximum distance from the centre to an edge
\end{fulllineitems}
\index{intersects() (GeoSpatialTools.octtree.SpaceTimeRectangle method)@\spxentry{intersects()}\spxextra{GeoSpatialTools.octtree.SpaceTimeRectangle method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.octtree.SpaceTimeRectangle.intersects}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{intersects}}}
{\sphinxparam{\DUrole{n}{other}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Test if another Rectangle object intersects this Rectangle
\begin{quote}\begin{description}
\sphinxlineitem{Return type}
\sphinxAtStartPar
\DUrole{sphinx_autodoc_typehints-type}{\sphinxcode{\sphinxupquote{bool}}}
\end{description}\end{quote}
\end{fulllineitems}
\index{lat (GeoSpatialTools.octtree.SpaceTimeRectangle property)@\spxentry{lat}\spxextra{GeoSpatialTools.octtree.SpaceTimeRectangle property}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.octtree.SpaceTimeRectangle.lat}}
\pysigstartsignatures
\pysigline
{\sphinxbfcode{\sphinxupquote{\DUrole{k}{property}\DUrole{w}{ }}}\sphinxbfcode{\sphinxupquote{lat}}\sphinxbfcode{\sphinxupquote{\DUrole{p}{:}\DUrole{w}{ }float}}}
\pysigstopsignatures
\sphinxAtStartPar
Centre latitude of the Rectangle
\end{fulllineitems}
\index{lat\_range (GeoSpatialTools.octtree.SpaceTimeRectangle property)@\spxentry{lat\_range}\spxextra{GeoSpatialTools.octtree.SpaceTimeRectangle property}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.octtree.SpaceTimeRectangle.lat_range}}
\pysigstartsignatures
\pysigline
{\sphinxbfcode{\sphinxupquote{\DUrole{k}{property}\DUrole{w}{ }}}\sphinxbfcode{\sphinxupquote{lat\_range}}\sphinxbfcode{\sphinxupquote{\DUrole{p}{:}\DUrole{w}{ }float}}}
\pysigstopsignatures
\sphinxAtStartPar
Latitude range of the Rectangle
\end{fulllineitems}
\index{lon (GeoSpatialTools.octtree.SpaceTimeRectangle property)@\spxentry{lon}\spxextra{GeoSpatialTools.octtree.SpaceTimeRectangle property}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.octtree.SpaceTimeRectangle.lon}}
\pysigstartsignatures
\pysigline
{\sphinxbfcode{\sphinxupquote{\DUrole{k}{property}\DUrole{w}{ }}}\sphinxbfcode{\sphinxupquote{lon}}\sphinxbfcode{\sphinxupquote{\DUrole{p}{:}\DUrole{w}{ }float}}}
\pysigstopsignatures
\sphinxAtStartPar
Centre longitude of the Rectangle
\end{fulllineitems}
\index{lon\_range (GeoSpatialTools.octtree.SpaceTimeRectangle property)@\spxentry{lon\_range}\spxextra{GeoSpatialTools.octtree.SpaceTimeRectangle property}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.octtree.SpaceTimeRectangle.lon_range}}
\pysigstartsignatures
\pysigline
{\sphinxbfcode{\sphinxupquote{\DUrole{k}{property}\DUrole{w}{ }}}\sphinxbfcode{\sphinxupquote{lon\_range}}\sphinxbfcode{\sphinxupquote{\DUrole{p}{:}\DUrole{w}{ }float}}}
\pysigstopsignatures
\sphinxAtStartPar
Longitude range of the Rectangle
\end{fulllineitems}
\index{nearby() (GeoSpatialTools.octtree.SpaceTimeRectangle method)@\spxentry{nearby()}\spxextra{GeoSpatialTools.octtree.SpaceTimeRectangle method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.octtree.SpaceTimeRectangle.nearby}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{nearby}}}
{\sphinxparam{\DUrole{n}{point}}\sphinxparamcomma \sphinxparam{\DUrole{n}{dist}}\sphinxparamcomma \sphinxparam{\DUrole{n}{t\_dist}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Check if point is nearby the Rectangle
\sphinxAtStartPar
Determines if a SpaceTimeRecord that falls on the surface of Earth is
nearby to the rectangle in space and time. This calculation uses the
Haversine distance metric.
\sphinxAtStartPar
Distance from rectangle to point is challenging on the surface of a
sphere, this calculation will return false positives as a check based
on the distance from the centre of the rectangle to the corners, or
to its Eastern edge (if the rectangle crosses the equator) is used in
combination with the input distance.
\sphinxAtStartPar
The primary use\sphinxhyphen{}case of this method is for querying an OctTree for
nearby Records.
\begin{quote}\begin{description}
\sphinxlineitem{Parameters}\begin{itemize}
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{point}} ({\hyperref[\detokenize{users_guide:GeoSpatialTools.octtree.SpaceTimeRecord}]{\sphinxcrossref{\sphinxstyleliteralemphasis{\sphinxupquote{SpaceTimeRecord}}}}})
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{dist}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}\sphinxstyleliteralemphasis{\sphinxupquote{,}})
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{t\_dist}} (\sphinxstyleliteralemphasis{\sphinxupquote{datetime.timedelta}})
\end{itemize}
\sphinxlineitem{Returns}
\sphinxAtStartPar
\sphinxstylestrong{bool}
\sphinxlineitem{Return type}
\sphinxAtStartPar
True if the point is \textless{}= dist + max(dist(centre, corners))
\end{description}\end{quote}
\end{fulllineitems}
\index{time\_range (GeoSpatialTools.octtree.SpaceTimeRectangle property)@\spxentry{time\_range}\spxextra{GeoSpatialTools.octtree.SpaceTimeRectangle property}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.octtree.SpaceTimeRectangle.time_range}}
\pysigstartsignatures
\pysigline
{\sphinxbfcode{\sphinxupquote{\DUrole{k}{property}\DUrole{w}{ }}}\sphinxbfcode{\sphinxupquote{time\_range}}\sphinxbfcode{\sphinxupquote{\DUrole{p}{:}\DUrole{w}{ }timedelta}}}
\pysigstopsignatures
\sphinxAtStartPar
The time extent of the Rectangle
\end{fulllineitems}
\end{fulllineitems}
\index{module@\spxentry{module}!GeoSpatialTools.kdtree@\spxentry{GeoSpatialTools.kdtree}}\index{GeoSpatialTools.kdtree@\spxentry{GeoSpatialTools.kdtree}!module@\spxentry{module}}
\section{KDTree}
\label{\detokenize{users_guide:kdtree}}\label{\detokenize{users_guide:module-GeoSpatialTools.kdtree}}
\sphinxAtStartPar
An implementation of KDTree using Haversine Distance for GeoSpatial analysis.
Useful tool for quickly searching for nearest neighbours. The implementation is
a K=2 or 2DTree as only 2 dimensions (longitude and latitude) are used.
\sphinxAtStartPar
Haversine distances are used for comparisons, so that the spherical geometry
of the earth is accounted for.
\index{KDTree (class in GeoSpatialTools.kdtree)@\spxentry{KDTree}\spxextra{class in GeoSpatialTools.kdtree}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.kdtree.KDTree}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{\DUrole{k}{class}\DUrole{w}{ }}}\sphinxcode{\sphinxupquote{GeoSpatialTools.kdtree.}}\sphinxbfcode{\sphinxupquote{KDTree}}}
{\sphinxparam{\DUrole{n}{points}}\sphinxparamcomma \sphinxparam{\DUrole{n}{depth}\DUrole{o}{=}\DUrole{default_value}{0}}\sphinxparamcomma \sphinxparam{\DUrole{n}{max\_depth}\DUrole{o}{=}\DUrole{default_value}{20}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
A Haverine distance implementation of a balanced KDTree.
\sphinxAtStartPar
This implementation is a \_balanced\_ KDTree, each leaf node should have the
same number of points (or differ by 1 depending on the number of points
the KDTree is intialised with).
\sphinxAtStartPar
The KDTree partitions in each of the lon and lat dimensions alternatively
in sequence by splitting at the median of the dimension of the points
assigned to the branch.
\begin{quote}\begin{description}
\sphinxlineitem{Parameters}\begin{itemize}
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{points}} (\sphinxstyleliteralemphasis{\sphinxupquote{list}}\sphinxstyleliteralemphasis{\sphinxupquote{{[}}}{\hyperref[\detokenize{users_guide:GeoSpatialTools.quadtree.Record}]{\sphinxcrossref{\sphinxstyleliteralemphasis{\sphinxupquote{Record}}}}}\sphinxstyleliteralemphasis{\sphinxupquote{{]}}}) \textendash{} A list of GeoSpatialTools.Record instances.
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{depth}} (\sphinxstyleliteralemphasis{\sphinxupquote{int}}) \textendash{} The current depth of the KDTree, you should set this to 0, it is used
internally.
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{max\_depth}} (\sphinxstyleliteralemphasis{\sphinxupquote{int}}) \textendash{} The maximium depth of the KDTree. The leaf nodes will have depth no
larger than this value. Leaf nodes will not be created if there is
only 1 point in the branch.
\end{itemize}
\end{description}\end{quote}
\index{delete() (GeoSpatialTools.kdtree.KDTree method)@\spxentry{delete()}\spxextra{GeoSpatialTools.kdtree.KDTree method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.kdtree.KDTree.delete}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{delete}}}
{\sphinxparam{\DUrole{n}{point}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Delete a Record from the KDTree. May unbalance the KDTree
\begin{quote}\begin{description}
\sphinxlineitem{Return type}
\sphinxAtStartPar
\DUrole{sphinx_autodoc_typehints-type}{\sphinxcode{\sphinxupquote{bool}}}
\end{description}\end{quote}
\end{fulllineitems}
\index{insert() (GeoSpatialTools.kdtree.KDTree method)@\spxentry{insert()}\spxextra{GeoSpatialTools.kdtree.KDTree method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.kdtree.KDTree.insert}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{insert}}}
{\sphinxparam{\DUrole{n}{point}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Insert a Record into the KDTree. May unbalance the KDTree.
\sphinxAtStartPar
The point will not be inserted if it is already in the KDTree.
\begin{quote}\begin{description}
\sphinxlineitem{Return type}
\sphinxAtStartPar
\DUrole{sphinx_autodoc_typehints-type}{\sphinxcode{\sphinxupquote{bool}}}
\end{description}\end{quote}
\end{fulllineitems}
\index{query() (GeoSpatialTools.kdtree.KDTree method)@\spxentry{query()}\spxextra{GeoSpatialTools.kdtree.KDTree method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.kdtree.KDTree.query}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{query}}}
{\sphinxparam{\DUrole{n}{point}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Find the nearest Record within the KDTree to a query Record
\begin{quote}\begin{description}
\sphinxlineitem{Return type}
\sphinxAtStartPar
\DUrole{sphinx_autodoc_typehints-type}{\sphinxcode{\sphinxupquote{Tuple}}{[}\sphinxcode{\sphinxupquote{List}}{[}{\hyperref[\detokenize{users_guide:GeoSpatialTools.quadtree.Record}]{\sphinxcrossref{\sphinxcode{\sphinxupquote{Record}}}}}{]}, \sphinxcode{\sphinxupquote{float}}{]}}
\end{description}\end{quote}
\end{fulllineitems}
\end{fulllineitems}
\index{module@\spxentry{module}!GeoSpatialTools.great\_circle@\spxentry{GeoSpatialTools.great\_circle}}\index{GeoSpatialTools.great\_circle@\spxentry{GeoSpatialTools.great\_circle}!module@\spxentry{module}}
\section{GreatCircle}
\label{\detokenize{users_guide:greatcircle}}\label{\detokenize{users_guide:module-GeoSpatialTools.great_circle}}
\sphinxAtStartPar
Constructors and methods for interacting with GreatCircle objects, including
comparisons between GreatCircle objects.
\index{GreatCircle (class in GeoSpatialTools.great\_circle)@\spxentry{GreatCircle}\spxextra{class in GeoSpatialTools.great\_circle}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.great_circle.GreatCircle}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{\DUrole{k}{class}\DUrole{w}{ }}}\sphinxcode{\sphinxupquote{GeoSpatialTools.great\_circle.}}\sphinxbfcode{\sphinxupquote{GreatCircle}}}
{\sphinxparam{\DUrole{n}{lon0}}\sphinxparamcomma \sphinxparam{\DUrole{n}{lat0}}\sphinxparamcomma \sphinxparam{\DUrole{n}{lon1}}\sphinxparamcomma \sphinxparam{\DUrole{n}{lat1}}\sphinxparamcomma \sphinxparam{\DUrole{n}{R}\DUrole{o}{=}\DUrole{default_value}{6371}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
A GreatCircle object for a pair of positions.
\sphinxAtStartPar
Construct a great circle path between a pair of positions.
\sphinxAtStartPar
\sphinxurl{https://www.boeing-727.com/Data/fly\%20odds/distance.html}
\begin{quote}\begin{description}
\sphinxlineitem{Parameters}\begin{itemize}
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lon0}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Longitude of start position.
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lat0}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Latitude of start position.
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lon1}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Longitude of end position.
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lat1}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Latitude of end position.
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{R}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Radius of the sphere. Default is Earth radius in km (6371.0).
\end{itemize}
\end{description}\end{quote}
\index{dist\_from\_point() (GeoSpatialTools.great\_circle.GreatCircle method)@\spxentry{dist\_from\_point()}\spxextra{GeoSpatialTools.great\_circle.GreatCircle method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.great_circle.GreatCircle.dist_from_point}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{dist\_from\_point}}}
{\sphinxparam{\DUrole{n}{lon}}\sphinxparamcomma \sphinxparam{\DUrole{n}{lat}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Compute distance from the GreatCircle to a point on the sphere.
\begin{quote}\begin{description}
\sphinxlineitem{Parameters}\begin{itemize}
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lon}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Longitude of the position to test.
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lat}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Longitude of the position to test.
\end{itemize}
\sphinxlineitem{Returns}
\sphinxAtStartPar
Minimum distance between point and the GreatCircle arc.
\sphinxlineitem{Return type}
\sphinxAtStartPar
float
\end{description}\end{quote}
\end{fulllineitems}
\index{intersection() (GeoSpatialTools.great\_circle.GreatCircle method)@\spxentry{intersection()}\spxextra{GeoSpatialTools.great\_circle.GreatCircle method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.great_circle.GreatCircle.intersection}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{intersection}}}
{\sphinxparam{\DUrole{n}{other}}\sphinxparamcomma \sphinxparam{\DUrole{n}{epsilon}\DUrole{o}{=}\DUrole{default_value}{0.01}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Determine intersection position with another GreatCircle.
\sphinxAtStartPar
Determine the location at which the GreatCircle intersects another
GreatCircle arc. (To within some epsilon threshold).
\sphinxAtStartPar
Returns \sphinxtitleref{None} if there is no solution \sphinxhyphen{} either because there is no
intersection point, or the planes generated from the arc and centre of
the sphere are identical.
\begin{quote}\begin{description}
\sphinxlineitem{Parameters}\begin{itemize}
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{other}} ({\hyperref[\detokenize{users_guide:GeoSpatialTools.great_circle.GreatCircle}]{\sphinxcrossref{\sphinxstyleliteralemphasis{\sphinxupquote{GreatCircle}}}}}) \textendash{} Intersecting GreatCircle object
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{epsilon}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Threshold for intersection
\end{itemize}
\sphinxlineitem{Returns}
\sphinxAtStartPar
Position of intersection
\sphinxlineitem{Return type}
\sphinxAtStartPar
(float, float) | None
\end{description}\end{quote}
\end{fulllineitems}
\index{intersection\_angle() (GeoSpatialTools.great\_circle.GreatCircle method)@\spxentry{intersection\_angle()}\spxextra{GeoSpatialTools.great\_circle.GreatCircle method}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.great_circle.GreatCircle.intersection_angle}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxbfcode{\sphinxupquote{intersection\_angle}}}
{\sphinxparam{\DUrole{n}{other}}\sphinxparamcomma \sphinxparam{\DUrole{n}{epsilon}\DUrole{o}{=}\DUrole{default_value}{0.01}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Get angle of intersection with another GreatCircle.
\sphinxAtStartPar
Get the angle of intersection with another GreatCircle arc. Returns
None if there is no intersection.
\sphinxAtStartPar
The intersection angle is computed using the normals of the planes
formed by the two intersecting great circle objects.
\begin{quote}\begin{description}
\sphinxlineitem{Parameters}\begin{itemize}
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{other}} ({\hyperref[\detokenize{users_guide:GeoSpatialTools.great_circle.GreatCircle}]{\sphinxcrossref{\sphinxstyleliteralemphasis{\sphinxupquote{GreatCircle}}}}}) \textendash{} Intersecting GreatCircle object
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{epsilon}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Threshold for intersection
\end{itemize}
\sphinxlineitem{Returns}
\sphinxAtStartPar
Intersection angle in degrees
\sphinxlineitem{Return type}
\sphinxAtStartPar
float | None
\end{description}\end{quote}
\end{fulllineitems}
\end{fulllineitems}
\index{cartesian\_to\_lonlat() (in module GeoSpatialTools.great\_circle)@\spxentry{cartesian\_to\_lonlat()}\spxextra{in module GeoSpatialTools.great\_circle}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.great_circle.cartesian_to_lonlat}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxcode{\sphinxupquote{GeoSpatialTools.great\_circle.}}\sphinxbfcode{\sphinxupquote{cartesian\_to\_lonlat}}}
{\sphinxparam{\DUrole{n}{x}}\sphinxparamcomma \sphinxparam{\DUrole{n}{y}}\sphinxparamcomma \sphinxparam{\DUrole{n}{z}}\sphinxparamcomma \sphinxparam{\DUrole{n}{to\_radians}\DUrole{o}{=}\DUrole{default_value}{False}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Get lon, and lat from cartesian coordinates.
\begin{quote}\begin{description}
\sphinxlineitem{Parameters}\begin{itemize}
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{x}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} x coordinate
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{y}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} y coordinate
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{z}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} z coordinate
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{to\_radians}} (\sphinxstyleliteralemphasis{\sphinxupquote{bool}}) \textendash{} Return angles in radians. Otherwise return values in degrees.
\end{itemize}
\sphinxlineitem{Return type}
\sphinxAtStartPar
\DUrole{sphinx_autodoc_typehints-type}{\sphinxcode{\sphinxupquote{tuple}}{[}\sphinxcode{\sphinxupquote{float}}, \sphinxcode{\sphinxupquote{float}}{]}}
\sphinxlineitem{Returns}
\sphinxAtStartPar
\begin{itemize}
\item {}
\sphinxAtStartPar
\sphinxstyleemphasis{(float, float)}
\item {}
\sphinxAtStartPar
\sphinxstyleemphasis{lon, lat}
\end{itemize}
\end{description}\end{quote}
\end{fulllineitems}
\index{polar\_to\_cartesian() (in module GeoSpatialTools.great\_circle)@\spxentry{polar\_to\_cartesian()}\spxextra{in module GeoSpatialTools.great\_circle}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.great_circle.polar_to_cartesian}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxcode{\sphinxupquote{GeoSpatialTools.great\_circle.}}\sphinxbfcode{\sphinxupquote{polar\_to\_cartesian}}}
{\sphinxparam{\DUrole{n}{lon}}\sphinxparamcomma \sphinxparam{\DUrole{n}{lat}}\sphinxparamcomma \sphinxparam{\DUrole{n}{R}\DUrole{o}{=}\DUrole{default_value}{6371}}\sphinxparamcomma \sphinxparam{\DUrole{n}{to\_radians}\DUrole{o}{=}\DUrole{default_value}{True}}\sphinxparamcomma \sphinxparam{\DUrole{n}{normalised}\DUrole{o}{=}\DUrole{default_value}{True}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Convert from polars coordinates to cartesian.
\sphinxAtStartPar
Get cartesian coordinates from spherical polar coordinates. Default
behaviour assumes lon and lat, so converts to radians. Set
\sphinxtitleref{to\_radians=False} if the coordinates are already in radians.
\begin{quote}\begin{description}
\sphinxlineitem{Parameters}\begin{itemize}
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lon}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Longitude.
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lat}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Latitude.
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{R}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Radius of sphere.
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{to\_radians}} (\sphinxstyleliteralemphasis{\sphinxupquote{bool}}) \textendash{} Convert lon and lat to radians.
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{normalised}} (\sphinxstyleliteralemphasis{\sphinxupquote{bool}}) \textendash{} Return normalised vector (ignore R value).
\end{itemize}
\sphinxlineitem{Returns}
\sphinxAtStartPar
x, y, z cartesian coordinates.
\sphinxlineitem{Return type}
\sphinxAtStartPar
(float, float, float)
\end{description}\end{quote}
\end{fulllineitems}
\index{module@\spxentry{module}!GeoSpatialTools.distance\_metrics@\spxentry{GeoSpatialTools.distance\_metrics}}\index{GeoSpatialTools.distance\_metrics@\spxentry{GeoSpatialTools.distance\_metrics}!module@\spxentry{module}}
\section{Distance Metrics}
\label{\detokenize{users_guide:distance-metrics}}\label{\detokenize{users_guide:module-GeoSpatialTools.distance_metrics}}
\sphinxAtStartPar
Functions for computing navigational information. Can be used to add
navigational information to DataFrames.
\index{bearing() (in module GeoSpatialTools.distance\_metrics)@\spxentry{bearing()}\spxextra{in module GeoSpatialTools.distance\_metrics}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.distance_metrics.bearing}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxcode{\sphinxupquote{GeoSpatialTools.distance\_metrics.}}\sphinxbfcode{\sphinxupquote{bearing}}}
{\sphinxparam{\DUrole{n}{lon0}}\sphinxparamcomma \sphinxparam{\DUrole{n}{lat0}}\sphinxparamcomma \sphinxparam{\DUrole{n}{lon1}}\sphinxparamcomma \sphinxparam{\DUrole{n}{lat1}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Compute the bearing of a track from (lon0, lat0) to (lon1, lat1).
\sphinxAtStartPar
Duplicated from geo\sphinxhyphen{}py
\begin{quote}\begin{description}
\sphinxlineitem{Parameters}\begin{itemize}
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lon0}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}\sphinxstyleliteralemphasis{\sphinxupquote{,}}) \textendash{} Longitude of start point
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lat0}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}\sphinxstyleliteralemphasis{\sphinxupquote{,}}) \textendash{} Latitude of start point
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lon1}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}\sphinxstyleliteralemphasis{\sphinxupquote{,}}) \textendash{} Longitude of target point
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lat1}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}\sphinxstyleliteralemphasis{\sphinxupquote{,}}) \textendash{} Latitude of target point
\end{itemize}
\sphinxlineitem{Returns}
\sphinxAtStartPar
\sphinxstylestrong{bearing} \textendash{} The bearing from point (lon0, lat0) to point (lon1, lat1) in degrees.
\sphinxlineitem{Return type}
\sphinxAtStartPar
float
\end{description}\end{quote}
\end{fulllineitems}
\index{destination() (in module GeoSpatialTools.distance\_metrics)@\spxentry{destination()}\spxextra{in module GeoSpatialTools.distance\_metrics}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.distance_metrics.destination}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxcode{\sphinxupquote{GeoSpatialTools.distance\_metrics.}}\sphinxbfcode{\sphinxupquote{destination}}}
{\sphinxparam{\DUrole{n}{lon}}\sphinxparamcomma \sphinxparam{\DUrole{n}{lat}}\sphinxparamcomma \sphinxparam{\DUrole{n}{bearing}}\sphinxparamcomma \sphinxparam{\DUrole{n}{distance}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Compute destination of a great circle path.
\sphinxAtStartPar
Compute the destination of a track started from ‘lon’, ‘lat’, with
‘bearing’. Distance is in units of km.
\sphinxAtStartPar
Duplicated from geo\sphinxhyphen{}py
\begin{quote}\begin{description}
\sphinxlineitem{Parameters}\begin{itemize}
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lon}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Longitude of initial position
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lat}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Latitude of initial position
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{bearing}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Direction of track
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{distance}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Distance to travel
\end{itemize}
\sphinxlineitem{Returns}
\sphinxAtStartPar
\sphinxstylestrong{destination} \textendash{} Longitude and Latitude of final position
\sphinxlineitem{Return type}
\sphinxAtStartPar
tuple{[}float, float{]}
\end{description}\end{quote}
\end{fulllineitems}
\index{gcd\_slc() (in module GeoSpatialTools.distance\_metrics)@\spxentry{gcd\_slc()}\spxextra{in module GeoSpatialTools.distance\_metrics}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.distance_metrics.gcd_slc}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxcode{\sphinxupquote{GeoSpatialTools.distance\_metrics.}}\sphinxbfcode{\sphinxupquote{gcd\_slc}}}
{\sphinxparam{\DUrole{n}{lon0}}\sphinxparamcomma \sphinxparam{\DUrole{n}{lat0}}\sphinxparamcomma \sphinxparam{\DUrole{n}{lon1}}\sphinxparamcomma \sphinxparam{\DUrole{n}{lat1}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Compute great circle distance on earth surface between two locations.
\begin{quote}\begin{description}
\sphinxlineitem{Parameters}\begin{itemize}
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lon0}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Longitude of position 0
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lat0}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Latitude of position 0
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lon1}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Longitude of position 1
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lat1}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Latitude of position 1
\end{itemize}
\sphinxlineitem{Returns}
\sphinxAtStartPar
\sphinxstylestrong{dist} \textendash{} Great circle distance between position 0 and position 1.
\sphinxlineitem{Return type}
\sphinxAtStartPar
float
\end{description}\end{quote}
\end{fulllineitems}
\index{haversine() (in module GeoSpatialTools.distance\_metrics)@\spxentry{haversine()}\spxextra{in module GeoSpatialTools.distance\_metrics}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.distance_metrics.haversine}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxcode{\sphinxupquote{GeoSpatialTools.distance\_metrics.}}\sphinxbfcode{\sphinxupquote{haversine}}}
{\sphinxparam{\DUrole{n}{lon0}}\sphinxparamcomma \sphinxparam{\DUrole{n}{lat0}}\sphinxparamcomma \sphinxparam{\DUrole{n}{lon1}}\sphinxparamcomma \sphinxparam{\DUrole{n}{lat1}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Compute Haversine distance between two points.
\begin{quote}\begin{description}
\sphinxlineitem{Parameters}\begin{itemize}
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lon0}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Longitude of position 0
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lat0}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Latitude of position 0
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lon1}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Longitude of position 1
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lat1}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Latitude of position 1
\end{itemize}
\sphinxlineitem{Returns}
\sphinxAtStartPar
\sphinxstylestrong{dist} \textendash{} Haversine distance between position 0 and position 1.
\sphinxlineitem{Return type}
\sphinxAtStartPar
float
\end{description}\end{quote}
\end{fulllineitems}
\index{midpoint() (in module GeoSpatialTools.distance\_metrics)@\spxentry{midpoint()}\spxextra{in module GeoSpatialTools.distance\_metrics}}
\begin{fulllineitems}
\phantomsection\label{\detokenize{users_guide:GeoSpatialTools.distance_metrics.midpoint}}
\pysigstartsignatures
\pysiglinewithargsret
{\sphinxcode{\sphinxupquote{GeoSpatialTools.distance\_metrics.}}\sphinxbfcode{\sphinxupquote{midpoint}}}
{\sphinxparam{\DUrole{n}{lon0}}\sphinxparamcomma \sphinxparam{\DUrole{n}{lat0}}\sphinxparamcomma \sphinxparam{\DUrole{n}{lon1}}\sphinxparamcomma \sphinxparam{\DUrole{n}{lat1}}}
{}
\pysigstopsignatures
\sphinxAtStartPar
Compute the midpoint of a great circle track
\begin{quote}\begin{description}
\sphinxlineitem{Parameters}\begin{itemize}
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lon0}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Longitude of position 0
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lat0}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Latitude of position 0
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lon1}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Longitude of position 1
\item {}
\sphinxAtStartPar
\sphinxstyleliteralstrong{\sphinxupquote{lat1}} (\sphinxstyleliteralemphasis{\sphinxupquote{float}}) \textendash{} Latitude of position 1
\end{itemize}
\sphinxlineitem{Returns}
\sphinxAtStartPar
Positions of midpoint between position 0 and position 1
\sphinxlineitem{Return type}
\sphinxAtStartPar
lon, lat
\end{description}\end{quote}
\end{fulllineitems}
\renewcommand{\indexname}{Python Module Index}
\begin{sphinxtheindex}
\let\bigletter\sphinxstyleindexlettergroup
\bigletter{g}
\item\relax\sphinxstyleindexentry{GeoSpatialTools.distance\_metrics}\sphinxstyleindexpageref{users_guide:\detokenize{module-GeoSpatialTools.distance_metrics}}
\item\relax\sphinxstyleindexentry{GeoSpatialTools.great\_circle}\sphinxstyleindexpageref{users_guide:\detokenize{module-GeoSpatialTools.great_circle}}
\item\relax\sphinxstyleindexentry{GeoSpatialTools.kdtree}\sphinxstyleindexpageref{users_guide:\detokenize{module-GeoSpatialTools.kdtree}}
\item\relax\sphinxstyleindexentry{GeoSpatialTools.neighbours}\sphinxstyleindexpageref{users_guide:\detokenize{module-GeoSpatialTools.neighbours}}
\item\relax\sphinxstyleindexentry{GeoSpatialTools.octtree}\sphinxstyleindexpageref{users_guide:\detokenize{module-GeoSpatialTools.octtree}}
\item\relax\sphinxstyleindexentry{GeoSpatialTools.quadtree}\sphinxstyleindexpageref{users_guide:\detokenize{module-GeoSpatialTools.quadtree}}
\end{sphinxtheindex}
\renewcommand{\indexname}{Index}
\printindex
\end{document}
\ No newline at end of file
...@@ -9,8 +9,8 @@ Welcome to GeoSpatialTool's documentation! ...@@ -9,8 +9,8 @@ Welcome to GeoSpatialTool's documentation!
.. toctree:: .. toctree::
:maxdepth: 4 :maxdepth: 4
:caption: Contents: :caption: Contents:
introduction introduction
getting_started getting_started
authors authors
users_guide users_guide
......
{ {
"cells": [ "cells": [
{
"cell_type": "markdown",
"id": "f7143f08-1d06-4e94-bbf6-ef35ddd11556",
"metadata": {},
"source": [
"# KDTree\n",
"\n",
"Testing the time to look-up nearby records with the `KDTree` implementation. Note that this implementation is actually a `2DTree` since it can only compute a valid distance comparison between longitude and latitude positions.\n",
"\n",
"The `KDTree` object is used for finding the closest neighbour to a position, in this implementation we use the Haversine distance to compare positions."
]
},
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 1, "execution_count": 1,
...@@ -8,11 +20,10 @@ ...@@ -8,11 +20,10 @@
"outputs": [], "outputs": [],
"source": [ "source": [
"import os\n", "import os\n",
"import gzip\n", "\n",
"os.environ[\"POLARS_MAX_THREADS\"] = \"4\"\n", "os.environ[\"POLARS_MAX_THREADS\"] = \"4\"\n",
"\n", "\n",
"from datetime import datetime, timedelta\n", "from datetime import datetime\n",
"from random import choice\n",
"from string import ascii_letters, digits\n", "from string import ascii_letters, digits\n",
"import random\n", "import random\n",
"import inspect\n", "import inspect\n",
...@@ -20,7 +31,17 @@ ...@@ -20,7 +31,17 @@
"import polars as pl\n", "import polars as pl\n",
"import numpy as np\n", "import numpy as np\n",
"\n", "\n",
"from GeoSpatialTools import Record, haversine, KDTree" "from GeoSpatialTools import Record, KDTree"
]
},
{
"cell_type": "markdown",
"id": "ec6c6e7f-8eee-47ea-a5e9-12537bb3412d",
"metadata": {},
"source": [
"## Set-up functions\n",
"\n",
"Used for generating data, or for comparisons by doing brute-force approach."
] ]
}, },
{ {
...@@ -31,6 +52,7 @@ ...@@ -31,6 +52,7 @@
"outputs": [], "outputs": [],
"source": [ "source": [
"def randnum() -> float:\n", "def randnum() -> float:\n",
" \"\"\"Get a random number between -1 and 1\"\"\"\n",
" return 2 * (np.random.rand() - 0.5)" " return 2 * (np.random.rand() - 0.5)"
] ]
}, },
...@@ -42,6 +64,7 @@ ...@@ -42,6 +64,7 @@
"outputs": [], "outputs": [],
"source": [ "source": [
"def generate_uid(n: int) -> str:\n", "def generate_uid(n: int) -> str:\n",
" \"\"\"Generates a pseudo uid by randomly selecting from characters\"\"\"\n",
" chars = ascii_letters + digits\n", " chars = ascii_letters + digits\n",
" return \"\".join(random.choice(chars) for _ in range(n))" " return \"\".join(random.choice(chars) for _ in range(n))"
] ]
...@@ -49,6 +72,179 @@ ...@@ -49,6 +72,179 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 4, "execution_count": 4,
"id": "9e647ecd-abdc-46a0-8261-aa081fda2e1d",
"metadata": {
"jupyter": {
"source_hidden": true
},
"scrolled": true
},
"outputs": [],
"source": [
"def check_cols(\n",
" df: pl.DataFrame | pl.LazyFrame,\n",
" cols: list[str],\n",
" var_name: str = \"dataframe\",\n",
") -> None:\n",
" \"\"\"\n",
" Check that a dataframe contains a list of columns. Raises an error if not.\n",
"\n",
" Parameters\n",
" ----------\n",
" df : polars Frame\n",
" Dataframe to check\n",
" cols : list[str]\n",
" Required columns\n",
" var_name : str\n",
" Name of the Frame - used for displaying in any error.\n",
" \"\"\"\n",
" calling_func = inspect.stack()[1][3]\n",
" if isinstance(df, pl.DataFrame):\n",
" have_cols = df.columns\n",
" elif isinstance(df, pl.LazyFrame):\n",
" have_cols = df.collect_schema().names()\n",
" else:\n",
" raise TypeError(\"Input Frame is not a polars Frame\")\n",
"\n",
" cols_in_frame = intersect(cols, have_cols)\n",
" missing = [c for c in cols if c not in cols_in_frame]\n",
"\n",
" if len(missing) > 0:\n",
" err_str = f\"({calling_func}) - {var_name} missing required columns. \"\n",
" err_str += f\"Require: {', '.join(cols)}. \"\n",
" err_str += f\"Missing: {', '.join(missing)}.\"\n",
" raise ValueError(err_str)\n",
"\n",
" return\n",
"\n",
"\n",
"def haversine_df(\n",
" df: pl.DataFrame | pl.LazyFrame,\n",
" lon: float,\n",
" lat: float,\n",
" R: float = 6371,\n",
" lon_col: str = \"lon\",\n",
" lat_col: str = \"lat\",\n",
") -> pl.DataFrame | pl.LazyFrame:\n",
" \"\"\"\n",
" Compute haversine distance on earth surface between lon-lat positions\n",
" in a polars DataFrame and a lon-lat position.\n",
"\n",
" Parameters\n",
" ----------\n",
" df : polars.DataFrame\n",
" The data, containing required columns:\n",
" * lon_col\n",
" * lat_col\n",
" * date_var\n",
" lon : float\n",
" The longitude of the position.\n",
" lat : float\n",
" The latitude of the position.\n",
" R : float\n",
" Radius of earth in km\n",
" lon_col : str\n",
" Name of the longitude column\n",
" lat_col : str\n",
" Name of the latitude column\n",
"\n",
" Returns\n",
" -------\n",
" polars.DataFrame\n",
" With additional column specifying distances between consecutive points\n",
" in the same units as 'R'. With colname defined by 'out_colname'.\n",
" \"\"\"\n",
" required_cols = [lon_col, lat_col]\n",
"\n",
" check_cols(df, required_cols, \"df\")\n",
" return (\n",
" df.with_columns(\n",
" [\n",
" pl.col(lat_col).radians().alias(\"_lat0\"),\n",
" pl.lit(lat).radians().alias(\"_lat1\"),\n",
" (pl.col(lon_col) - lon).radians().alias(\"_dlon\"),\n",
" (pl.col(lat_col) - lat).radians().alias(\"_dlat\"),\n",
" ]\n",
" )\n",
" .with_columns(\n",
" (\n",
" (pl.col(\"_dlat\") / 2).sin().pow(2)\n",
" + pl.col(\"_lat0\").cos()\n",
" * pl.col(\"_lat1\").cos()\n",
" * (pl.col(\"_dlon\") / 2).sin().pow(2)\n",
" ).alias(\"_a\")\n",
" )\n",
" .with_columns(\n",
" (2 * R * (pl.col(\"_a\").sqrt().arcsin())).round(2).alias(\"_dist\")\n",
" )\n",
" .drop([\"_lat0\", \"_lat1\", \"_dlon\", \"_dlat\", \"_a\"])\n",
" )\n",
"\n",
"\n",
"def intersect(a, b) -> set:\n",
" \"\"\"Intersection of a and b, items in both a and b\"\"\"\n",
" return set(a) & set(b)\n",
"\n",
"\n",
"def nearest_ship(\n",
" lon: float,\n",
" lat: float,\n",
" df: pl.DataFrame,\n",
" lon_col: str = \"lon\",\n",
" lat_col: str = \"lat\",\n",
") -> pl.DataFrame:\n",
" \"\"\"\n",
" Find the observation nearest to a position in space.\n",
"\n",
" Get a frame with only the records that is closest to the input point.\n",
"\n",
" Parameters\n",
" ----------\n",
" lon : float\n",
" The longitude of the position.\n",
" lat : float\n",
" The latitude of the position.\n",
" df : polars.DataFrame\n",
" The pool of records to search. Can be pre-filtered and filter_datetime\n",
" set to False.\n",
" lon_col : str\n",
" Name of the longitude column in the pool DataFrame\n",
" lat_col : str\n",
" Name of the latitude column in the pool DataFrame\n",
"\n",
" Returns\n",
" -------\n",
" polars.DataFrame\n",
" Containing only records from the pool within max_dist of the input\n",
" point, optionally at the same datetime if filter_datetime is True.\n",
" \"\"\"\n",
" required_cols = [lon_col, lat_col]\n",
" check_cols(df, required_cols, \"df\")\n",
"\n",
" return (\n",
" df.pipe(\n",
" haversine_df,\n",
" lon=lon,\n",
" lat=lat,\n",
" lon_col=lon_col,\n",
" lat_col=lat_col,\n",
" )\n",
" .filter(pl.col(\"_dist\").eq(pl.col(\"_dist\").min()))\n",
" .drop([\"_dist\"])\n",
" )"
]
},
{
"cell_type": "markdown",
"id": "287bdc1d-1ecf-4c59-af95-d2dc639c6894",
"metadata": {},
"source": [
"## Initialise random data"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "c60b30de-f864-477a-a09a-5f1caa4d9b9a", "id": "c60b30de-f864-477a-a09a-5f1caa4d9b9a",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
...@@ -58,17 +254,17 @@ ...@@ -58,17 +254,17 @@
"text": [ "text": [
"(16000, 2)\n", "(16000, 2)\n",
"shape: (5, 2)\n", "shape: (5, 2)\n",
"┌─────┬─────┐\n", "┌─────┬─────┐\n",
"│ lon ┆ lat │\n", "│ lon ┆ lat │\n",
"│ --- ┆ --- │\n", "│ --- ┆ --- │\n",
"│ i64 ┆ i64 │\n", "│ i64 ┆ i64 │\n",
"╞═════╪═════╡\n", "╞═════╪═════╡\n",
"│ 127 ┆ 21 │\n", "│ -26 ┆ -42 │\n",
"│ -148 ┆ 36 │\n", "│ 109 ┆ -33 │\n",
"│ -46 ┆ -15 │\n", "│ -87 ┆ -18 │\n",
"│ 104 ┆ 89 │\n", "│ -94 ┆ -81 │\n",
"│ -57 ┆ -31 │\n", "│ -94 ┆ 0 │\n",
"└─────┴─────┘\n" "└─────┴─────┘\n"
] ]
} }
], ],
...@@ -76,7 +272,12 @@ ...@@ -76,7 +272,12 @@
"N = 16_000\n", "N = 16_000\n",
"lons = pl.int_range(-180, 180, eager=True)\n", "lons = pl.int_range(-180, 180, eager=True)\n",
"lats = pl.int_range(-90, 90, eager=True)\n", "lats = pl.int_range(-90, 90, eager=True)\n",
"dates = pl.datetime_range(datetime(1900, 1, 1, 0), datetime(1900, 1, 31, 23), interval=\"1h\", eager=True)\n", "dates = pl.datetime_range(\n",
" datetime(1900, 1, 1, 0),\n",
" datetime(1900, 1, 31, 23),\n",
" interval=\"1h\",\n",
" eager=True,\n",
")\n",
"\n", "\n",
"lons_use = lons.sample(N, with_replacement=True).alias(\"lon\")\n", "lons_use = lons.sample(N, with_replacement=True).alias(\"lon\")\n",
"lats_use = lats.sample(N, with_replacement=True).alias(\"lat\")\n", "lats_use = lats.sample(N, with_replacement=True).alias(\"lat\")\n",
...@@ -90,7 +291,7 @@ ...@@ -90,7 +291,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 5, "execution_count": 6,
"id": "875f2a67-49fe-476f-add1-b1d76c6cd8f9", "id": "875f2a67-49fe-476f-add1-b1d76c6cd8f9",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
...@@ -98,9 +299,19 @@ ...@@ -98,9 +299,19 @@
"records = [Record(**r) for r in df.rows(named=True)]" "records = [Record(**r) for r in df.rows(named=True)]"
] ]
}, },
{
"cell_type": "markdown",
"id": "bd83330b-ef2c-478e-9a7b-820454d198bb",
"metadata": {},
"source": [
"## Initialise the `KDTree`\n",
"\n",
"There is an overhead to constructing a `KDTree` object, so performance improvement is only for multiple comparisons."
]
},
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 6, "execution_count": 7,
"id": "1e883e5a-5086-4c29-aff2-d308874eae16", "id": "1e883e5a-5086-4c29-aff2-d308874eae16",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
...@@ -108,8 +319,8 @@ ...@@ -108,8 +319,8 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"CPU times: user 151 ms, sys: 360 ms, total: 511 ms\n", "CPU times: user 35 ms, sys: 1.5 ms, total: 36.5 ms\n",
"Wall time: 57.3 ms\n" "Wall time: 32.1 ms\n"
] ]
} }
], ],
...@@ -118,9 +329,30 @@ ...@@ -118,9 +329,30 @@
"kt = KDTree(records)" "kt = KDTree(records)"
] ]
}, },
{
"cell_type": "markdown",
"id": "0a37ef06-2691-4e01-96a9-1c1ecd582599",
"metadata": {},
"source": [
"## Compare with brute force approach"
]
},
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 7, "execution_count": 8,
"id": "365bbf30-7a93-438d-92b2-a3471f1e9249",
"metadata": {},
"outputs": [],
"source": [
"test_record = Record(\n",
" random.choice(range(-179, 180)) + randnum(),\n",
" random.choice(range(-89, 90)) + randnum(),\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "69022ad1-5ec8-4a09-836c-273ef452451f", "id": "69022ad1-5ec8-4a09-836c-273ef452451f",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
...@@ -128,19 +360,18 @@ ...@@ -128,19 +360,18 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"203 μs ± 4.56 μs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)\n" "101 μs ± 3.52 μs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)\n"
] ]
} }
], ],
"source": [ "source": [
"%%timeit\n", "%%timeit\n",
"test_record = Record(random.choice(range(-179, 180)) + randnum(), random.choice(range(-89, 90)) + randnum())\n",
"kt.query(test_record)" "kt.query(test_record)"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 8, "execution_count": 10,
"id": "28031966-c7d0-4201-a467-37590118e851", "id": "28031966-c7d0-4201-a467-37590118e851",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
...@@ -148,19 +379,45 @@ ...@@ -148,19 +379,45 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"8.87 ms ± 188 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" "8.17 ms ± 38.6 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n"
] ]
} }
], ],
"source": [ "source": [
"%%timeit\n", "%%timeit\n",
"test_record = Record(random.choice(range(-179, 180)) + randnum(), random.choice(range(-89, 90)) + randnum())\n",
"np.argmin([test_record.distance(p) for p in records])" "np.argmin([test_record.distance(p) for p in records])"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 9, "execution_count": 11,
"id": "09e0f923-ca49-47bf-8643-e0b3a6d0467c",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"8.22 ms ± 95.3 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n"
]
}
],
"source": [
"%%timeit\n",
"nearest_ship(lon=test_record.lon, lat=test_record.lat, df=df)"
]
},
{
"cell_type": "markdown",
"id": "f0359950-942d-45ea-8676-b22c8ce9e296",
"metadata": {},
"source": [
"## Verify that results are correct"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "0d10b2ba-57b2-475c-9d01-135363423990", "id": "0d10b2ba-57b2-475c-9d01-135363423990",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
...@@ -168,8 +425,8 @@ ...@@ -168,8 +425,8 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"CPU times: user 17.4 s, sys: 147 ms, total: 17.6 s\n", "CPU times: user 16.3 s, sys: 74.7 ms, total: 16.4 s\n",
"Wall time: 17.6 s\n" "Wall time: 16.4 s\n"
] ]
} }
], ],
...@@ -177,18 +434,28 @@ ...@@ -177,18 +434,28 @@
"%%time\n", "%%time\n",
"n_samples = 1000\n", "n_samples = 1000\n",
"tol = 1e-8\n", "tol = 1e-8\n",
"test_records = [Record(random.choice(range(-179, 180)) + randnum(), random.choice(range(-89, 90)) + randnum()) for _ in range(n_samples)]\n", "test_records = [\n",
" Record(\n",
" random.choice(range(-179, 180)) + randnum(),\n",
" random.choice(range(-89, 90)) + randnum(),\n",
" )\n",
" for _ in range(n_samples)\n",
"]\n",
"kd_res = [kt.query(r) for r in test_records]\n", "kd_res = [kt.query(r) for r in test_records]\n",
"kd_recs = [_[0][0] for _ in kd_res]\n", "kd_recs = [_[0][0] for _ in kd_res]\n",
"kd_dists = [_[1] for _ in kd_res]\n", "kd_dists = [_[1] for _ in kd_res]\n",
"tr_recs = [records[np.argmin([r.distance(p) for p in records])] for r in test_records]\n", "tr_recs = [\n",
" records[np.argmin([r.distance(p) for p in records])] for r in test_records\n",
"]\n",
"tr_dists = [min([r.distance(p) for p in records]) for r in test_records]\n", "tr_dists = [min([r.distance(p) for p in records]) for r in test_records]\n",
"assert all([abs(k - t) < tol for k, t in zip(kd_dists, tr_dists)]), \"NOT MATCHING?\"" "\n",
"if not all([abs(k - t) < tol for k, t in zip(kd_dists, tr_dists)]):\n",
" raise ValueError(\"NOT MATCHING?\")"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 10, "execution_count": 13,
"id": "a6aa6926-7fd5-4fff-bd20-7bc0305b948d", "id": "a6aa6926-7fd5-4fff-bd20-7bc0305b948d",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
...@@ -214,7 +481,7 @@ ...@@ -214,7 +481,7 @@
"└──────────┴──────────┴─────────┴────────┴────────┴─────────┴────────┴────────┘" "└──────────┴──────────┴─────────┴────────┴────────┴─────────┴────────┴────────┘"
] ]
}, },
"execution_count": 10, "execution_count": 13,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
...@@ -229,23 +496,25 @@ ...@@ -229,23 +496,25 @@
"tr_lons = [r.lon for r in tr_recs]\n", "tr_lons = [r.lon for r in tr_recs]\n",
"tr_lats = [r.lat for r in tr_recs]\n", "tr_lats = [r.lat for r in tr_recs]\n",
"\n", "\n",
"df = pl.DataFrame({\n", "df = pl.DataFrame(\n",
" \"test_lon\": test_lons, \n", " {\n",
" \"test_lat\": test_lats,\n", " \"test_lon\": test_lons,\n",
" \"kd_dist\": kd_dists,\n", " \"test_lat\": test_lats,\n",
" \"kd_lon\": kd_lons,\n", " \"kd_dist\": kd_dists,\n",
" \"kd_lat\": kd_lats,\n", " \"kd_lon\": kd_lons,\n",
" \"tr_dist\": tr_dists,\n", " \"kd_lat\": kd_lats,\n",
" \"tr_lon\": tr_lons,\n", " \"tr_dist\": tr_dists,\n",
" \"tr_lat\": tr_lats, \n", " \"tr_lon\": tr_lons,\n",
"}).filter((pl.col(\"kd_dist\") - pl.col(\"tr_dist\")).abs().ge(tol))\n", " \"tr_lat\": tr_lats,\n",
" }\n",
").filter((pl.col(\"kd_dist\") - pl.col(\"tr_dist\")).abs().ge(tol))\n",
"df" "df"
] ]
} }
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "GeoSpatialTools", "display_name": "geospatialtools",
"language": "python", "language": "python",
"name": "geospatialtools" "name": "geospatialtools"
}, },
...@@ -259,7 +528,7 @@ ...@@ -259,7 +528,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.12.7" "version": "3.11.11"
} }
}, },
"nbformat": 4, "nbformat": 4,
......
...@@ -7,7 +7,9 @@ ...@@ -7,7 +7,9 @@
"source": [ "source": [
"## OctTree!\n", "## OctTree!\n",
"\n", "\n",
"Testing the time to look-up nearby records with the PyCOADS OctTree implementation." "Testing the time to look-up nearby records with the `OctTree` implementation.\n",
"\n",
"The `OctTree` is used to find records within a spatio-temporal range of a given point, or within a box defined by lon, lat, & time bounds."
] ]
}, },
{ {
...@@ -18,11 +20,10 @@ ...@@ -18,11 +20,10 @@
"outputs": [], "outputs": [],
"source": [ "source": [
"import os\n", "import os\n",
"import gzip\n", "\n",
"os.environ[\"POLARS_MAX_THREADS\"] = \"4\"\n", "os.environ[\"POLARS_MAX_THREADS\"] = \"4\"\n",
"\n", "\n",
"from datetime import datetime, timedelta\n", "from datetime import datetime, timedelta\n",
"from random import choice\n",
"from string import ascii_letters, digits\n", "from string import ascii_letters, digits\n",
"import random\n", "import random\n",
"import inspect\n", "import inspect\n",
...@@ -30,289 +31,26 @@ ...@@ -30,289 +31,26 @@
"import polars as pl\n", "import polars as pl\n",
"import numpy as np\n", "import numpy as np\n",
"\n", "\n",
"from GeoSpatialTools.octtree import OctTree, SpaceTimeRecord as Record, SpaceTimeRectangle as Rectangle" "from GeoSpatialTools.octtree import (\n",
] " OctTree,\n",
}, " SpaceTimeRecord as Record,\n",
{ " SpaceTimeRectangle as Rectangle,\n",
"cell_type": "raw", ")"
"id": "99295bad-0db3-444b-8d38-acc7875cc0f0",
"metadata": {},
"source": [
"## Generate Data\n",
"\n",
"16,000 rows of data"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "d8f1e5e1-513c-4bdf-a9f9-cef9562a7cb7",
"metadata": {},
"outputs": [],
"source": [
"def generate_uid(n: int) -> str:\n",
" chars = ascii_letters + digits\n",
" return \"\".join(random.choice(chars) for _ in range(n))"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "986d9cc5-e610-449a-9ee7-e281b7558ca9",
"metadata": {},
"outputs": [],
"source": [
"N = 16_000\n",
"lons = pl.int_range(-180, 180, eager=True)\n",
"lats = pl.int_range(-90, 90, eager=True)\n",
"dates = pl.datetime_range(datetime(1900, 1, 1, 0), datetime(1900, 1, 31, 23), interval=\"1h\", eager=True)\n",
"\n",
"lons_use = lons.sample(N, with_replacement=True).alias(\"lon\")\n",
"lats_use = lats.sample(N, with_replacement=True).alias(\"lat\")\n",
"dates_use = dates.sample(N, with_replacement=True).alias(\"datetime\")\n",
"uids = pl.Series(\"uid\", [generate_uid(8) for _ in range(N)])\n",
"\n",
"df = pl.DataFrame([lons_use, lats_use, dates_use, uids]).unique()"
]
},
{
"cell_type": "markdown",
"id": "237096f1-093e-49f0-9a9a-2bec5231726f",
"metadata": {},
"source": [
"## Add extra rows\n",
"\n",
"For testing larger datasets. Uncomment to use."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "0b8fd425-8a90-4f76-91b7-60df48aa98e4",
"metadata": {},
"outputs": [],
"source": [
"# _df = df.clone()\n",
"# for i in range(100):\n",
"# df2 = pl.DataFrame([\n",
"# _df[\"lon\"].shuffle(),\n",
"# _df[\"lat\"].shuffle(),\n",
"# _df[\"datetime\"].shuffle(),\n",
"# _df[\"uid\"].shuffle(),\n",
"# ]).with_columns(pl.concat_str([pl.col(\"uid\"), pl.lit(f\"{i:03d}\")]).alias(\"uid\"))\n",
"# df = df.vstack(df2)\n",
"# df.shape\n",
"# df"
]
},
{
"cell_type": "markdown",
"id": "c7bd16e0-96a6-426b-b00a-7c3b8a2aaddd",
"metadata": {},
"source": [
"## Intialise the OctTree Object"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "af06a976-ff52-49e0-a886-91bcbe540ffe",
"metadata": {},
"outputs": [],
"source": [
"otree = OctTree(Rectangle(-180, 180, -90, 90, datetime(1900, 1, 1, 0), datetime(1900, 1, 31, 23)), capacity = 10, max_depth = 25)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "2ba99b37-787c-4862-8075-a7596208c60e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 106 ms, sys: 3.98 ms, total: 110 ms\n",
"Wall time: 109 ms\n"
]
}
],
"source": [
"%%time\n",
"for r in df.rows():\n",
" otree.insert(Record(*r))"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "59d38446-f7d2-4eec-bba3-c39bd7279623",
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"OctTree:\n",
"- boundary: SpaceTimeRectangle(west=-180, east=180, south=-90, north=90, start=datetime.datetime(1900, 1, 1, 0, 0), end=datetime.datetime(1900, 1, 31, 23, 0))\n",
"- capacity: 10\n",
"- depth: 0\n",
"- max_depth: 25\n",
"- contents:\n",
"- number of elements: 10\n",
" * SpaceTimeRecord(x = 92, y = 15, datetime = 1900-01-17 08:00:00, uid = HRF401hH)\n",
" * SpaceTimeRecord(x = -35, y = 37, datetime = 1900-01-04 08:00:00, uid = CXZaSOdh)\n",
" * SpaceTimeRecord(x = 84, y = -7, datetime = 1900-01-07 16:00:00, uid = 2aEjxGwG)\n",
" * SpaceTimeRecord(x = 68, y = 73, datetime = 1900-01-18 17:00:00, uid = Ah7lanWB)\n",
" * SpaceTimeRecord(x = -179, y = 40, datetime = 1900-01-01 11:00:00, uid = HGxSJzf4)\n",
" * SpaceTimeRecord(x = -73, y = 23, datetime = 1900-01-09 12:00:00, uid = qHQ8opO9)\n",
" * SpaceTimeRecord(x = 117, y = -23, datetime = 1900-01-31 06:00:00, uid = ctvs56Fq)\n",
" * SpaceTimeRecord(x = 109, y = 55, datetime = 1900-01-13 14:00:00, uid = C2xXIglD)\n",
" * SpaceTimeRecord(x = 104, y = -10, datetime = 1900-01-06 16:00:00, uid = WEpQKIOV)\n",
" * SpaceTimeRecord(x = 45, y = -71, datetime = 1900-01-29 00:00:00, uid = 7r1UeXRi)\n",
"- with children:\n",
" OctTree:\n",
" - boundary: SpaceTimeRectangle(west=-180, east=0.0, south=0.0, north=90, start=datetime.datetime(1900, 1, 1, 0, 0), end=datetime.datetime(1900, 1, 16, 11, 30))\n",
" - capacity: 10\n",
" - depth: 1\n",
" - max_depth: 25\n",
" - contents:\n",
" - number of elements: 10\n",
" * SpaceTimeRecord(x = -84, y = 38, datetime = 1900-01-15 10:00:00, uid = 63mpq3Kx)\n",
" * SpaceTimeRecord(x = -78, y = 60, datetime = 1900-01-10 01:00:00, uid = vZ8HLu5t)\n",
" * SpaceTimeRecord(x = -89, y = 24, datetime = 1900-01-12 17:00:00, uid = gn2o9tYQ)\n",
" * SpaceTimeRecord(x = -149, y = 7, datetime = 1900-01-08 11:00:00, uid = 2ODnGJO6)\n",
" * SpaceTimeRecord(x = -37, y = 54, datetime = 1900-01-12 13:00:00, uid = 11cApOwm)\n",
" * SpaceTimeRecord(x = -34, y = 88, datetime = 1900-01-03 05:00:00, uid = 8SN6zPWh)\n",
" * SpaceTimeRecord(x = -36, y = 13, datetime = 1900-01-14 13:00:00, uid = ijfjmp8E)\n",
" * SpaceTimeRecord(x = -168, y = 62, datetime = 1900-01-03 09:00:00, uid = Cc4m1azR)\n",
" * SpaceTimeRecord(x = -76, y = 67, datetime = 1900-01-06 04:00:00, uid = 4WeWpZUz)\n",
" * SpaceTimeRecord(x = -156, y = 39, datetime = 1900-01-13 10:00:00, uid = dZXAMaXq)\n",
" - with children:\n",
" OctTree:\n",
" - boundary: SpaceTimeRectangle(west=-180, east=-90.0, south=45.0, north=90, start=datetime.datetime(1900, 1, 1, 0, 0), end=datetime.datetime(1900, 1, 8, 17, 45))\n",
" - capacity: 10\n",
" - depth: 2\n",
" - max_depth: 25\n",
" - contents:\n",
" - number of elements: 10\n",
" * SpaceTimeRecord(x = -141, y = 79, datetime = 1900-01-03 05:00:00, uid = mN1Mg7Vn)\n",
" * SpaceTimeRecord(x = -172, y = 80, datetime = 1900-01-01 14:00:00, uid = NBBZ3bCW)\n",
" * SpaceTimeRecord(x = -93, y = 53, datetime = 1900-01-06 07:00:00, uid = jX8HZPJT)\n",
" * SpaceTimeRecord(x = -168, y = 82, datetime = 1900-01-03 08:00:00, uid = dlxpN1Ew)\n",
" * SpaceTimeRecord(x = -111, y = 83, datetime = 1900-01-02 12:00:00, uid = GXLopHH0)\n",
" * SpaceTimeRecord(x = -178, y = 61, datetime = 1900-01-02 00:00:00, uid = 0ut6CLe5)\n",
" * SpaceTimeRecord(x = -148, y = 74, datetime = 1900-01-07 23:00:00, uid = xUySW1tx)\n",
" * SpaceTimeRecord(x = -174, y = 63, datetime = 1900-01-06 22:00:00, uid = 8sI94Lt6)\n",
" * SpaceTimeRecord(x = -114, y = 84, datetime = 1900-01-05 15:00:00, uid = OoY9mEkQ)\n",
" * SpaceTimeRecord(x = -102, y = 82, datetime = 1900-01-02 15:00:00, uid = bd4sLang)\n",
" - with children:\n",
" OctTree:\n",
" - boundary: SpaceTimeRectangle(west=-180, east=-135.0, south=67.5, north=90, start=datetime.datetime(1900, 1, 1, 0, 0), end=datetime.datetime(1900, 1, 4, 20, 52, 30))\n",
" - capacity: 10\n",
" - depth: 3\n",
" - max_depth: 25\n",
" - contents:\n",
" - number of elements: 10\n",
" * SpaceTimeRecord(x = -148, y = 79, datetime = 1900-01-03 21:00:00, uid = kNWm70rm)\n",
" * SpaceTimeRecord(x = -157, y = 80, datetime = 1900-01-03 05:00:00, uid = 471X27tA)\n",
" * SpaceTimeRecord(x = -152, y = 85, datetime = 1900-01-03 01:00:00, uid = cjTyQn7E)\n",
" * SpaceTimeRecord(x = -154, y = 88, datetime = 1900-01-03 15:00:00, uid = JTnjCJZN)\n",
" * SpaceTimeRecord(x = -139, y = 83, datetime = 1900-01-01 21:00:00, uid = kZ28j8I5)\n",
" * SpaceTimeRecord(x = -161, y = 73, datetime = 1900-01-03 02:00:00, uid = wsHJBLLC)\n",
" * SpaceTimeRecord(x = -140, y = 71, datetime = 1900-01-02 07:00:00, uid = 4bTg1N2k)\n",
" * SpaceTimeRecord(x = -141, y = 74, datetime = 1900-01-04 09:00:00, uid = I6M8kuue)\n",
" * SpaceTimeRecord(x = -144, y = 72, datetime = 1900-01-04 17:00:00, uid = 0fPvYOC9)\n",
" * SpaceTimeRecord(x = -157, y = 78, datetime = 1900-01-03 16:00:00, uid = yAL3OeaK)\n",
" - with children:\n",
" OctTree:\n",
" - boundary: SpaceTimeRectangle(west=-180, east=-157.5, south=78.75, north=90, start=datetime.datetime(1900, 1, 1, 0, 0), end=datetime.datetime(1900, 1, 2, 22, 26, 15))\n",
" - capacity: 10\n",
" - depth: 4\n",
" - max_depth: 25\n",
" - contents:\n",
" - number of elements: 4\n",
" * SpaceTimeRecord(x = -180, y = 88, datetime = 1900-01-02 12:00:00, uid = CXeAd3y4)\n",
" * SpaceTimeRecord(x = -180, y = 87, datetime = 1900-01-01 16:00:00, uid = TB2xKFgK)\n",
" * SpaceTimeRecord(x = -171, y = 79, datetime = 1900-01-02 04:00:00, uid = pIU8qvxT)\n",
" * SpaceTimeRecord(x = -168, y = 85, datetime = 1900-01-01 22:00:00, uid = 7zL4gz8K)\n",
" OctTree:\n",
" - boundary: SpaceTimeRectangle(west=-157.5, east=-135.0, south=78.75, north=90, start=datetime.datetime(1900, 1, 1, 0, 0), end=datetime.datetime(1900, 1, 2, 22, 26, 15))\n",
" - capacity: 10\n",
" - depth: 4\n",
" - max_depth: 25\n",
" - contents:\n",
" - number of elements: 2\n",
" * SpaceTimeRecord(x = -149, y = 82, datetime = 1900-01-01 20:00:00, uid = xTYMs6Xp)\n",
" * SpaceTimeRecord(x = -154, y = 84, datetime = 1900-01-02 21:00:00, uid = JSEaGBsn)\n",
" OctTree:\n",
" - boundary: SpaceTimeRectangle(west=-180, east=-157.5, south=67.5, north=78.75, start=datetime.datetime(1900, 1, 1, 0, 0), end=datetime.datetime(1900, 1, 2, 22, 26, 15))\n",
" - capacity: 10\n",
" - depth: 4\n",
" - max_depth: 25\n",
" - contents:\n",
" - number of elements: 3\n",
" * SpaceTimeRecord(x = -173, y = 75, datetime = 1900-01-01 06:00:00, uid = M4N3amQ3)\n"
]
}
],
"source": [
"s = str(otree)\n",
"print(\"\\n\".join(s.split(\"\\n\")[:100]))"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "6b02c2ea-6566-47c2-97e0-43d8b18e0713", "id": "6b0e8015-b958-4be7-9e63-9e21f081011b",
"metadata": {}, "metadata": {},
"source": [ "source": [
"## Time Execution\n", "## Set-up functions\n",
"\n", "\n",
"Testing the identification of nearby points against the original full search" "For comparisons using brute-force approach"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "094b588c-e938-4838-9719-1defdfff74fa",
"metadata": {},
"outputs": [],
"source": [
"dts = pl.datetime_range(datetime(1900, 1, 1), datetime(1900, 2, 1), interval=\"1h\", eager=True, closed=\"left\")\n",
"N = dts.len()\n",
"lons = 180 - 360 * np.random.rand(N)\n",
"lats = 90 - 180 * np.random.rand(N)\n",
"test_df = pl.DataFrame({\"lon\": lons, \"lat\": lats, \"datetime\": dts})\n",
"test_recs = [Record(*r) for r in test_df.rows()]\n",
"dt = timedelta(days = 1)\n",
"dist = 350"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "66a48b86-d449-45d2-9837-2b3e07f5563d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"207 μs ± 6.25 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)\n"
]
}
],
"source": [
"%%timeit\n",
"otree.nearby_points(random.choice(test_recs), dist=dist, t_dist=dt)"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 10, "execution_count": 2,
"id": "972d4a16-39fd-4f80-8592-1c5d5cabf5be", "id": "972d4a16-39fd-4f80-8592-1c5d5cabf5be",
"metadata": { "metadata": {
"jupyter": { "jupyter": {
...@@ -334,7 +72,7 @@ ...@@ -334,7 +72,7 @@
" df : polars Frame\n", " df : polars Frame\n",
" Dataframe to check\n", " Dataframe to check\n",
" cols : list[str]\n", " cols : list[str]\n",
" Requried columns\n", " Required columns\n",
" var_name : str\n", " var_name : str\n",
" Name of the Frame - used for displaying in any error.\n", " Name of the Frame - used for displaying in any error.\n",
" \"\"\"\n", " \"\"\"\n",
...@@ -351,9 +89,8 @@ ...@@ -351,9 +89,8 @@
"\n", "\n",
" if len(missing) > 0:\n", " if len(missing) > 0:\n",
" err_str = f\"({calling_func}) - {var_name} missing required columns. \"\n", " err_str = f\"({calling_func}) - {var_name} missing required columns. \"\n",
" err_str += f'Require: {\", \".join(cols)}. '\n", " err_str += f\"Require: {', '.join(cols)}. \"\n",
" err_str += f'Missing: {\", \".join(missing)}.'\n", " err_str += f\"Missing: {', '.join(missing)}.\"\n",
" logging.error(err_str)\n",
" raise ValueError(err_str)\n", " raise ValueError(err_str)\n",
"\n", "\n",
" return\n", " return\n",
...@@ -361,24 +98,15 @@ ...@@ -361,24 +98,15 @@
"\n", "\n",
"def haversine_df(\n", "def haversine_df(\n",
" df: pl.DataFrame | pl.LazyFrame,\n", " df: pl.DataFrame | pl.LazyFrame,\n",
" date_var: str = \"datetime\",\n", " lon: float,\n",
" lat: float,\n",
" R: float = 6371,\n", " R: float = 6371,\n",
" reverse: bool = False,\n",
" out_colname: str = \"dist\",\n",
" lon_col: str = \"lon\",\n", " lon_col: str = \"lon\",\n",
" lat_col: str = \"lat\",\n", " lat_col: str = \"lat\",\n",
" lon2_col: str | None = None,\n",
" lat2_col: str | None = None,\n",
" sorted: bool = False,\n",
" rev_prefix: str = \"rev_\",\n",
") -> pl.DataFrame | pl.LazyFrame:\n", ") -> pl.DataFrame | pl.LazyFrame:\n",
" \"\"\"\n", " \"\"\"\n",
" Compute haversine distance on earth surface between lon-lat positions.\n", " Compute haversine distance on earth surface between lon-lat positions\n",
"\n", " in a polars DataFrame and a lon-lat position.\n",
" If only 'lon_col' and 'lat_col' are specified then this computes the\n",
" distance between consecutive points. If a second set of positions is\n",
" included via the optional 'lon2_col' and 'lat2_col' arguments then the\n",
" distances between the columns are computed.\n",
"\n", "\n",
" Parameters\n", " Parameters\n",
" ----------\n", " ----------\n",
...@@ -387,27 +115,16 @@ ...@@ -387,27 +115,16 @@
" * lon_col\n", " * lon_col\n",
" * lat_col\n", " * lat_col\n",
" * date_var\n", " * date_var\n",
" date_var : str\n", " lon : float\n",
" Name of the datetime column on which to sort the positions\n", " The longitude of the position.\n",
" lat : float\n",
" The latitude of the position.\n",
" R : float\n", " R : float\n",
" Radius of earth in km\n", " Radius of earth in km\n",
" reverse : bool\n",
" Compute distances in reverse\n",
" out_colname : str\n",
" Name of the output column to store distances. Prefixed with 'rev_' if\n",
" reverse is True\n",
" lon_col : str\n", " lon_col : str\n",
" Name of the longitude column\n", " Name of the longitude column\n",
" lat_col : str\n", " lat_col : str\n",
" Name of the latitude column\n", " Name of the latitude column\n",
" lon2_col : str\n",
" Name of the 2nd longitude column if present\n",
" lat2_col : str\n",
" Name of the 2nd latitude column if present\n",
" sorted : bool\n",
" Compute distances assuming that the frame is already sorted\n",
" rev_prefix : str\n",
" Prefix to use for colnames if reverse is True\n",
"\n", "\n",
" Returns\n", " Returns\n",
" -------\n", " -------\n",
...@@ -417,61 +134,14 @@ ...@@ -417,61 +134,14 @@
" \"\"\"\n", " \"\"\"\n",
" required_cols = [lon_col, lat_col]\n", " required_cols = [lon_col, lat_col]\n",
"\n", "\n",
" if lon2_col is not None and lat2_col is not None:\n",
" required_cols += [lon2_col, lat2_col]\n",
" check_cols(df, required_cols, \"df\")\n",
" return (\n",
" df.with_columns(\n",
" [\n",
" pl.col(lat_col).radians().alias(\"_lat0\"),\n",
" pl.col(lat2_col).radians().alias(\"_lat1\"),\n",
" (pl.col(lon_col) - pl.col(lon2_col))\n",
" .radians()\n",
" .alias(\"_dlon\"),\n",
" (pl.col(lat_col) - pl.col(lat2_col))\n",
" .radians()\n",
" .alias(\"_dlat\"),\n",
" ]\n",
" )\n",
" .with_columns(\n",
" (\n",
" (pl.col(\"_dlat\") / 2).sin().pow(2)\n",
" + pl.col(\"_lat0\").cos()\n",
" * pl.col(\"_lat1\").cos()\n",
" * (pl.col(\"_dlon\") / 2).sin().pow(2)\n",
" ).alias(\"_a\")\n",
" )\n",
" .with_columns(\n",
" (2 * R * (pl.col(\"_a\").sqrt().arcsin()))\n",
" .round(2)\n",
" .alias(out_colname)\n",
" )\n",
" .drop([\"_lat0\", \"_lat1\", \"_dlon\", \"_dlat\", \"_a\"])\n",
" )\n",
"\n",
" if lon2_col is not None or lat2_col is not None:\n",
" logging.warning(\n",
" \"(haversine_df) 2nd position incorrectly specified. \"\n",
" + \"Calculating consecutive distances.\"\n",
" )\n",
"\n",
" required_cols += [date_var]\n",
" check_cols(df, required_cols, \"df\")\n", " check_cols(df, required_cols, \"df\")\n",
" if reverse:\n",
" out_colname = rev_prefix + out_colname\n",
" if not sorted:\n",
" df = df.sort(date_var, descending=reverse)\n",
" return (\n", " return (\n",
" df.with_columns(\n", " df.with_columns(\n",
" [\n", " [\n",
" pl.col(lat_col).radians().alias(\"_lat0\"),\n", " pl.col(lat_col).radians().alias(\"_lat0\"),\n",
" pl.col(lat_col).shift(n=-1).radians().alias(\"_lat1\"),\n", " pl.lit(lat).radians().alias(\"_lat1\"),\n",
" (pl.col(lon_col).shift(n=-1) - pl.col(lon_col))\n", " (pl.col(lon_col) - lon).radians().alias(\"_dlon\"),\n",
" .radians()\n", " (pl.col(lat_col) - lat).radians().alias(\"_dlat\"),\n",
" .alias(\"_dlon\"),\n",
" (pl.col(lat_col).shift(n=-1) - pl.col(lat_col))\n",
" .radians()\n",
" .alias(\"_dlat\"),\n",
" ]\n", " ]\n",
" )\n", " )\n",
" .with_columns(\n", " .with_columns(\n",
...@@ -483,17 +153,17 @@ ...@@ -483,17 +153,17 @@
" ).alias(\"_a\")\n", " ).alias(\"_a\")\n",
" )\n", " )\n",
" .with_columns(\n", " .with_columns(\n",
" (2 * R * (pl.col(\"_a\").sqrt().arcsin()))\n", " (2 * R * (pl.col(\"_a\").sqrt().arcsin())).round(2).alias(\"_dist\")\n",
" .round(2)\n",
" .fill_null(strategy=\"forward\")\n",
" .alias(out_colname)\n",
" )\n", " )\n",
" .drop([\"_lat0\", \"_lat1\", \"_dlon\", \"_dlat\", \"_a\"])\n", " .drop([\"_lat0\", \"_lat1\", \"_dlon\", \"_dlat\", \"_a\"])\n",
" )\n", " )\n",
"\n", "\n",
"\n",
"def intersect(a, b) -> set:\n", "def intersect(a, b) -> set:\n",
" \"\"\"Intersection of a and b, items in both a and b\"\"\"\n",
" return set(a) & set(b)\n", " return set(a) & set(b)\n",
"\n", "\n",
"\n",
"def nearby_ships(\n", "def nearby_ships(\n",
" lon: float,\n", " lon: float,\n",
" lat: float,\n", " lat: float,\n",
...@@ -573,26 +243,201 @@ ...@@ -573,26 +243,201 @@
" )\n", " )\n",
"\n", "\n",
" return (\n", " return (\n",
" pool.with_columns(\n", " pool.pipe(\n",
" [pl.lit(lon).alias(\"_lon\"), pl.lit(lat).alias(\"_lat\")]\n",
" )\n",
" .pipe(\n",
" haversine_df,\n", " haversine_df,\n",
" lon=lon,\n",
" lat=lat,\n",
" lon_col=lon_col,\n", " lon_col=lon_col,\n",
" lat_col=lat_col,\n", " lat_col=lat_col,\n",
" out_colname=\"_dist\",\n",
" lon2_col=\"_lon\",\n",
" lat2_col=\"_lat\",\n",
" )\n", " )\n",
" .filter(pl.col(\"_dist\").le(max_dist))\n", " .filter(pl.col(\"_dist\").le(max_dist))\n",
" .drop([\"_dist\", \"_lon\", \"_lat\"])\n", " .drop([\"_dist\"])\n",
" )\n" " )"
]
},
{
"cell_type": "markdown",
"id": "08cba819-1ebe-48b3-85c7-3fd7469399f8",
"metadata": {},
"source": [
"## Generate Data\n",
"\n",
"16,000 rows of data"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 11, "execution_count": 3,
"id": "8b9279ed-6f89-4423-8833-acd0b365eb7b", "id": "d8f1e5e1-513c-4bdf-a9f9-cef9562a7cb7",
"metadata": {},
"outputs": [],
"source": [
"def generate_uid(n: int) -> str:\n",
" \"\"\"Generates a pseudo uid by randomly selecting from characters\"\"\"\n",
" chars = ascii_letters + digits\n",
" return \"\".join(random.choice(chars) for _ in range(n))"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "986d9cc5-e610-449a-9ee7-e281b7558ca9",
"metadata": {},
"outputs": [],
"source": [
"N = 16_000\n",
"lons = pl.int_range(-180, 180, eager=True)\n",
"lats = pl.int_range(-90, 90, eager=True)\n",
"dates = pl.datetime_range(\n",
" datetime(1900, 1, 1, 0),\n",
" datetime(1900, 1, 31, 23),\n",
" interval=\"1h\",\n",
" eager=True,\n",
")\n",
"\n",
"lons_use = lons.sample(N, with_replacement=True).alias(\"lon\")\n",
"lats_use = lats.sample(N, with_replacement=True).alias(\"lat\")\n",
"dates_use = dates.sample(N, with_replacement=True).alias(\"datetime\")\n",
"uids = pl.Series(\"uid\", [generate_uid(8) for _ in range(N)])\n",
"\n",
"df = pl.DataFrame([lons_use, lats_use, dates_use, uids]).unique()"
]
},
{
"cell_type": "markdown",
"id": "237096f1-093e-49f0-9a9a-2bec5231726f",
"metadata": {},
"source": [
"## Add extra rows\n",
"\n",
"For testing larger datasets. Uncomment to use."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "0b8fd425-8a90-4f76-91b7-60df48aa98e4",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div><style>\n",
".dataframe > thead > tr,\n",
".dataframe > tbody > tr {\n",
" text-align: right;\n",
" white-space: pre-wrap;\n",
"}\n",
"</style>\n",
"<small>shape: (1_616_000, 4)</small><table border=\"1\" class=\"dataframe\"><thead><tr><th>lon</th><th>lat</th><th>datetime</th><th>uid</th></tr><tr><td>i64</td><td>i64</td><td>datetime[μs]</td><td>str</td></tr></thead><tbody><tr><td>-152</td><td>-90</td><td>1900-01-16 16:00:00</td><td>&quot;OIcjzfs5&quot;</td></tr><tr><td>-177</td><td>8</td><td>1900-01-29 01:00:00</td><td>&quot;xt99511W&quot;</td></tr><tr><td>0</td><td>35</td><td>1900-01-17 06:00:00</td><td>&quot;2E2Zp4SU&quot;</td></tr><tr><td>-54</td><td>-75</td><td>1900-01-08 12:00:00</td><td>&quot;wceONvH7&quot;</td></tr><tr><td>50</td><td>83</td><td>1900-01-27 10:00:00</td><td>&quot;nwuthgEW&quot;</td></tr><tr><td>&hellip;</td><td>&hellip;</td><td>&hellip;</td><td>&hellip;</td></tr><tr><td>-29</td><td>-89</td><td>1900-01-03 23:00:00</td><td>&quot;q8Ue8Uzx099&quot;</td></tr><tr><td>11</td><td>45</td><td>1900-01-31 05:00:00</td><td>&quot;6wKA4HVs099&quot;</td></tr><tr><td>-172</td><td>51</td><td>1900-01-06 17:00:00</td><td>&quot;MuIp2NeA099&quot;</td></tr><tr><td>33</td><td>70</td><td>1900-01-23 10:00:00</td><td>&quot;7dNMyTOj099&quot;</td></tr><tr><td>124</td><td>2</td><td>1900-01-15 20:00:00</td><td>&quot;CoQtomTB099&quot;</td></tr></tbody></table></div>"
],
"text/plain": [
"shape: (1_616_000, 4)\n",
"┌──────┬─────┬─────────────────────┬─────────────┐\n",
"│ lon ┆ lat ┆ datetime ┆ uid │\n",
"│ --- ┆ --- ┆ --- ┆ --- │\n",
"│ i64 ┆ i64 ┆ datetime[μs] ┆ str │\n",
"╞══════╪═════╪═════════════════════╪═════════════╡\n",
"│ -152 ┆ -90 ┆ 1900-01-16 16:00:00 ┆ OIcjzfs5 │\n",
"│ -177 ┆ 8 ┆ 1900-01-29 01:00:00 ┆ xt99511W │\n",
"│ 0 ┆ 35 ┆ 1900-01-17 06:00:00 ┆ 2E2Zp4SU │\n",
"│ -54 ┆ -75 ┆ 1900-01-08 12:00:00 ┆ wceONvH7 │\n",
"│ 50 ┆ 83 ┆ 1900-01-27 10:00:00 ┆ nwuthgEW │\n",
"│ … ┆ … ┆ … ┆ … │\n",
"│ -29 ┆ -89 ┆ 1900-01-03 23:00:00 ┆ q8Ue8Uzx099 │\n",
"│ 11 ┆ 45 ┆ 1900-01-31 05:00:00 ┆ 6wKA4HVs099 │\n",
"│ -172 ┆ 51 ┆ 1900-01-06 17:00:00 ┆ MuIp2NeA099 │\n",
"│ 33 ┆ 70 ┆ 1900-01-23 10:00:00 ┆ 7dNMyTOj099 │\n",
"│ 124 ┆ 2 ┆ 1900-01-15 20:00:00 ┆ CoQtomTB099 │\n",
"└──────┴─────┴─────────────────────┴─────────────┘"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"_df = df.clone()\n",
"for i in range(100):\n",
" df2 = pl.DataFrame(\n",
" [\n",
" _df[\"lon\"].shuffle(),\n",
" _df[\"lat\"].shuffle(),\n",
" _df[\"datetime\"].shuffle(),\n",
" _df[\"uid\"].shuffle(),\n",
" ]\n",
" ).with_columns(\n",
" pl.concat_str([pl.col(\"uid\"), pl.lit(f\"{i:03d}\")]).alias(\"uid\")\n",
" )\n",
" df = df.vstack(df2)\n",
"df.shape\n",
"df"
]
},
{
"cell_type": "markdown",
"id": "c7bd16e0-96a6-426b-b00a-7c3b8a2aaddd",
"metadata": {},
"source": [
"## Initialise the OctTree Object\n",
"\n",
"There is an overhead in constructing the `OctTree` class. The performance benefits appear if multiple neighbourhood searches are performed."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "af06a976-ff52-49e0-a886-91bcbe540ffe",
"metadata": {},
"outputs": [],
"source": [
"bounds = Rectangle(\n",
" -180,\n",
" 180,\n",
" -90,\n",
" 90,\n",
" datetime(1900, 1, 1, 0),\n",
" datetime(1900, 1, 31, 23),\n",
")\n",
"otree = OctTree(bounds, capacity=10, max_depth=25)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "2ba99b37-787c-4862-8075-a7596208c60e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 16.3 s, sys: 253 ms, total: 16.6 s\n",
"Wall time: 16.6 s\n"
]
}
],
"source": [
"%%time\n",
"for r in df.rows():\n",
" otree.insert(Record(*r))"
]
},
{
"cell_type": "markdown",
"id": "94be9d8a-02fc-49f2-98c9-0bcf250b1d10",
"metadata": {},
"source": [
"### View the `OctTree`\n",
"\n",
"It is a nested object, with child `OctTree` objects in each octant of the space-time domain."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "59d38446-f7d2-4eec-bba3-c39bd7279623",
"metadata": { "metadata": {
"scrolled": true "scrolled": true
}, },
...@@ -601,47 +446,228 @@ ...@@ -601,47 +446,228 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"5.36 ms ± 164 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" "OctTree:\n",
"- boundary: SpaceTimeRectangle(west=-180, east=180, south=-90, north=90, start=datetime.datetime(1900, 1, 1, 0, 0), end=datetime.datetime(1900, 1, 31, 23, 0))\n",
"- capacity: 10\n",
"- depth: 0\n",
"- max_depth: 25\n",
"- contents:\n",
"- number of elements: 10\n",
" * SpaceTimeRecord(x = -152, y = -90, datetime = 1900-01-16 16:00:00, uid = OIcjzfs5)\n",
" * SpaceTimeRecord(x = -177, y = 8, datetime = 1900-01-29 01:00:00, uid = xt99511W)\n",
" * SpaceTimeRecord(x = 0, y = 35, datetime = 1900-01-17 06:00:00, uid = 2E2Zp4SU)\n",
" * SpaceTimeRecord(x = -54, y = -75, datetime = 1900-01-08 12:00:00, uid = wceONvH7)\n",
" * SpaceTimeRecord(x = 50, y = 83, datetime = 1900-01-27 10:00:00, uid = nwuthgEW)\n",
" * SpaceTimeRecord(x = -158, y = 56, datetime = 1900-01-05 09:00:00, uid = svHopi53)\n",
" * SpaceTimeRecord(x = 61, y = -31, datetime = 1900-01-30 06:00:00, uid = wQSNr6C4)\n",
" * SpaceTimeRecord(x = 20, y = 54, datetime = 1900-01-19 23:00:00, uid = X0tJnSvA)\n",
" * SpaceTimeRecord(x = -6, y = 20, datetime = 1900-01-22 19:00:00, uid = 0K8N7iN6)\n",
" * SpaceTimeRecord(x = -129, y = -9, datetime = 1900-01-14 11:00:00, uid = 7yXQTGb0)\n",
"- with children:\n",
" OctTree:\n",
" - boundary: SpaceTimeRectangle(west=-180, east=0.0, south=0.0, north=90, start=datetime.datetime(1900, 1, 1, 0, 0), end=datetime.datetime(1900, 1, 16, 11, 30))\n",
" - capacity: 10\n",
" - depth: 1\n",
" - max_depth: 25\n",
" - contents:\n",
" - number of elements: 10\n",
" * SpaceTimeRecord(x = -166, y = 34, datetime = 1900-01-11 04:00:00, uid = vXHJHSW5)\n",
" * SpaceTimeRecord(x = -13, y = 21, datetime = 1900-01-05 20:00:00, uid = KgAn9Jbx)\n",
" * SpaceTimeRecord(x = -84, y = 88, datetime = 1900-01-06 06:00:00, uid = ViZnTg8Z)\n",
" * SpaceTimeRecord(x = -165, y = 37, datetime = 1900-01-08 23:00:00, uid = LAjiwrGC)\n",
" * SpaceTimeRecord(x = -132, y = 57, datetime = 1900-01-01 03:00:00, uid = dOI2LWaC)\n",
" * SpaceTimeRecord(x = -156, y = 22, datetime = 1900-01-11 01:00:00, uid = RK8e20dA)\n",
" * SpaceTimeRecord(x = -154, y = 56, datetime = 1900-01-09 08:00:00, uid = KqJoV8UA)\n",
" * SpaceTimeRecord(x = -119, y = 52, datetime = 1900-01-02 10:00:00, uid = UtEIgcof)\n",
" * SpaceTimeRecord(x = -161, y = 63, datetime = 1900-01-10 10:00:00, uid = QjifGPIx)\n",
" * SpaceTimeRecord(x = -133, y = 87, datetime = 1900-01-05 07:00:00, uid = qtolX8oT)\n",
" - with children:\n",
" OctTree:\n",
" - boundary: SpaceTimeRectangle(west=-180, east=-90.0, south=45.0, north=90, start=datetime.datetime(1900, 1, 1, 0, 0), end=datetime.datetime(1900, 1, 8, 17, 45))\n",
" - capacity: 10\n",
" - depth: 2\n",
" - max_depth: 25\n",
" - contents:\n",
" - number of elements: 10\n",
" * SpaceTimeRecord(x = -129, y = 54, datetime = 1900-01-05 04:00:00, uid = dZ2BgU6A)\n",
" * SpaceTimeRecord(x = -96, y = 67, datetime = 1900-01-07 11:00:00, uid = KK38iUxT)\n",
" * SpaceTimeRecord(x = -137, y = 50, datetime = 1900-01-04 15:00:00, uid = Mlj2MvCb)\n",
" * SpaceTimeRecord(x = -134, y = 74, datetime = 1900-01-02 04:00:00, uid = wCj1pLL6)\n",
" * SpaceTimeRecord(x = -158, y = 86, datetime = 1900-01-06 04:00:00, uid = sIPv5QW4)\n",
" * SpaceTimeRecord(x = -113, y = 56, datetime = 1900-01-08 02:00:00, uid = 5hnlO6ce)\n",
" * SpaceTimeRecord(x = -166, y = 54, datetime = 1900-01-08 12:00:00, uid = PXj9tTWn)\n",
" * SpaceTimeRecord(x = -97, y = 83, datetime = 1900-01-06 11:00:00, uid = bizLnSZk)\n",
" * SpaceTimeRecord(x = -126, y = 53, datetime = 1900-01-05 13:00:00, uid = guzXcRwq)\n",
" * SpaceTimeRecord(x = -148, y = 55, datetime = 1900-01-02 05:00:00, uid = CizwDlIJ)\n",
" - with children:\n",
" OctTree:\n",
" - boundary: SpaceTimeRectangle(west=-180, east=-135.0, south=67.5, north=90, start=datetime.datetime(1900, 1, 1, 0, 0), end=datetime.datetime(1900, 1, 4, 20, 52, 30))\n",
" - capacity: 10\n",
" - depth: 3\n",
" - max_depth: 25\n",
" - contents:\n",
" - number of elements: 10\n",
" * SpaceTimeRecord(x = -144, y = 83, datetime = 1900-01-03 20:00:00, uid = L5hq9qlG)\n",
" * SpaceTimeRecord(x = -138, y = 84, datetime = 1900-01-03 11:00:00, uid = DnDQBPlt)\n",
" * SpaceTimeRecord(x = -140, y = 78, datetime = 1900-01-01 11:00:00, uid = VVKqwc7O)\n",
" * SpaceTimeRecord(x = -179, y = 85, datetime = 1900-01-02 01:00:00, uid = fd5NWtYa)\n",
" * SpaceTimeRecord(x = -179, y = 86, datetime = 1900-01-04 11:00:00, uid = KJp276Fa)\n",
" * SpaceTimeRecord(x = -176, y = 68, datetime = 1900-01-02 06:00:00, uid = nWy0gnK4)\n",
" * SpaceTimeRecord(x = -136, y = 77, datetime = 1900-01-01 06:00:00, uid = 21nlHCJg)\n",
" * SpaceTimeRecord(x = -171, y = 82, datetime = 1900-01-04 11:00:00, uid = 5uBXalIT)\n",
" * SpaceTimeRecord(x = -145, y = 85, datetime = 1900-01-03 16:00:00, uid = fjG42lVI)\n",
" * SpaceTimeRecord(x = -155, y = 70, datetime = 1900-01-01 13:00:00, uid = lev3XeV1)\n",
" - with children:\n",
" OctTree:\n",
" - boundary: SpaceTimeRectangle(west=-180, east=-157.5, south=78.75, north=90, start=datetime.datetime(1900, 1, 1, 0, 0), end=datetime.datetime(1900, 1, 2, 22, 26, 15))\n",
" - capacity: 10\n",
" - depth: 4\n",
" - max_depth: 25\n",
" - contents:\n",
" - number of elements: 10\n",
" * SpaceTimeRecord(x = -169, y = 85, datetime = 1900-01-02 17:00:00, uid = xdVWmHGw)\n",
" * SpaceTimeRecord(x = -176, y = 87, datetime = 1900-01-01 03:00:00, uid = M6ActtYJ)\n",
" * SpaceTimeRecord(x = -171, y = 89, datetime = 1900-01-02 22:00:00, uid = Xw7p5CU0)\n",
" * SpaceTimeRecord(x = -166, y = 86, datetime = 1900-01-01 13:00:00, uid = Ini19LT9)\n",
" * SpaceTimeRecord(x = -166, y = 87, datetime = 1900-01-02 07:00:00, uid = 77vJfw2i000)\n",
" * SpaceTimeRecord(x = -164, y = 83, datetime = 1900-01-01 15:00:00, uid = tSjaLRdX000)\n",
" * SpaceTimeRecord(x = -171, y = 81, datetime = 1900-01-01 04:00:00, uid = xNRbwxLR000)\n",
" * SpaceTimeRecord(x = -177, y = 87, datetime = 1900-01-02 14:00:00, uid = 26OOsEav000)\n",
" * SpaceTimeRecord(x = -163, y = 80, datetime = 1900-01-02 08:00:00, uid = 80kZku0r000)\n",
" * SpaceTimeRecord(x = -174, y = 83, datetime = 1900-01-02 06:00:00, uid = TORItCvU001)\n",
" - with children:\n",
" OctTree:\n",
" - boundary: SpaceTimeRectangle(west=-180, east=-168.75, south=84.375, north=90, start=datetime.datetime(1900, 1, 1, 0, 0), end=datetime.datetime(1900, 1, 1, 23, 13, 7, 500000))\n",
" - capacity: 10\n",
" - depth: 5\n",
" - max_depth: 25\n",
" - contents:\n",
" - number of elements: 10\n",
" * SpaceTimeRecord(x = -178, y = 86, datetime = 1900-01-01 19:00:00, uid = iuABumaA002)\n",
" * SpaceTimeRecord(x = -172, y = 88, datetime = 1900-01-01 09:00:00, uid = 7H12EJwT002)\n",
" * SpaceTimeRecord(x = -176, y = 85, datetime = 1900-01-01 13:00:00, uid = A2rVtlYK006)\n"
] ]
} }
], ],
"source": [ "source": [
"%%timeit\n", "s = str(otree)\n",
"rec = random.choice(test_recs)\n", "print(\"\\n\".join(s.split(\"\\n\")[:100]))"
"nearby_ships(lon=rec.lon, lat=rec.lat, dt=rec.datetime, max_dist=dist, dt_gap=dt, date_col=\"datetime\", pool=df, filter_datetime=True)"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "d148f129-9d8c-4c46-8f01-3e9c1e93e81a", "id": "6b02c2ea-6566-47c2-97e0-43d8b18e0713",
"metadata": {}, "metadata": {},
"source": [ "source": [
"## Verify\n", "## Time Execution\n",
"\n", "\n",
"Check that records are the same" "Testing the identification of nearby points against the original full search"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 12, "execution_count": 9,
"id": "11f3d73a-fbe5-4f27-88d8-d0d687bd0eac", "id": "094b588c-e938-4838-9719-1defdfff74fa",
"metadata": {}, "metadata": {},
"outputs": [],
"source": [
"dts = pl.datetime_range(\n",
" datetime(1900, 1, 1),\n",
" datetime(1900, 2, 1),\n",
" interval=\"1h\",\n",
" eager=True,\n",
" closed=\"left\",\n",
")\n",
"N = dts.len()\n",
"lons = 180 - 360 * np.random.rand(N)\n",
"lats = 90 - 180 * np.random.rand(N)\n",
"test_df = pl.DataFrame({\"lon\": lons, \"lat\": lats, \"datetime\": dts})\n",
"test_recs = [Record(*r) for r in test_df.rows()]\n",
"dt = timedelta(days=1)\n",
"dist = 350"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "66a48b86-d449-45d2-9837-2b3e07f5563d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1.28 ms ± 28.2 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)\n"
]
}
],
"source": [
"%%timeit\n",
"otree.nearby_points(random.choice(test_recs), dist=dist, t_dist=dt)"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "8b9279ed-6f89-4423-8833-acd0b365eb7b",
"metadata": {
"scrolled": true
},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"CPU times: user 2.52 s, sys: 253 ms, total: 2.78 s\n", "11.8 ms ± 198 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n"
"Wall time: 2.66 s\n"
] ]
} }
], ],
"source": [
"%%timeit\n",
"rec = random.choice(test_recs)\n",
"nearby_ships(\n",
" lon=rec.lon,\n",
" lat=rec.lat,\n",
" dt=rec.datetime,\n",
" max_dist=dist,\n",
" dt_gap=dt,\n",
" date_col=\"datetime\",\n",
" pool=df,\n",
" filter_datetime=True,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "d148f129-9d8c-4c46-8f01-3e9c1e93e81a",
"metadata": {},
"source": [
"## Verify\n",
"\n",
"Check that records are the same"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "11f3d73a-fbe5-4f27-88d8-d0d687bd0eac",
"metadata": {},
"outputs": [],
"source": [ "source": [
"%%time\n", "%%time\n",
"dist = 250\n", "dist = 250\n",
"for _ in range(250):\n", "for _ in range(250):\n",
" rec = Record(*random.choice(df.rows()))\n", " rec = Record(*random.choice(df.rows()))\n",
" orig = nearby_ships(lon=rec.lon, lat=rec.lat, dt=rec.datetime, max_dist=dist, dt_gap=dt, date_col=\"datetime\", pool=df, filter_datetime=True)\n", " orig = nearby_ships(\n",
" lon=rec.lon,\n",
" lat=rec.lat,\n",
" dt=rec.datetime,\n",
" max_dist=dist,\n",
" dt_gap=dt,\n",
" date_col=\"datetime\",\n",
" pool=df,\n",
" filter_datetime=True,\n",
" ) # noqa\n",
" tree = otree.nearby_points(rec, dist=dist, t_dist=dt)\n", " tree = otree.nearby_points(rec, dist=dist, t_dist=dt)\n",
" if orig.height > 0:\n", " if orig.height > 0:\n",
" if not tree:\n", " if not tree:\n",
...@@ -649,7 +675,16 @@ ...@@ -649,7 +675,16 @@
" print(\"NO TREE!\")\n", " print(\"NO TREE!\")\n",
" print(f\"{orig = }\")\n", " print(f\"{orig = }\")\n",
" else:\n", " else:\n",
" tree = pl.from_records([(r.lon, r.lat, r.datetime, r.uid) for r in tree], orient=\"row\").rename({\"column_0\": \"lon\", \"column_1\": \"lat\", \"column_2\": \"datetime\", \"column_3\": \"uid\"})\n", " tree = pl.from_records(\n",
" [(r.lon, r.lat, r.datetime, r.uid) for r in tree], orient=\"row\"\n",
" ).rename(\n",
" {\n",
" \"column_0\": \"lon\",\n",
" \"column_1\": \"lat\",\n",
" \"column_2\": \"datetime\",\n",
" \"column_3\": \"uid\",\n",
" }\n",
" ) # noqa\n",
" if tree.height != orig.height:\n", " if tree.height != orig.height:\n",
" print(\"Tree and Orig Heights Do Not Match\")\n", " print(\"Tree and Orig Heights Do Not Match\")\n",
" print(f\"{orig = }\")\n", " print(f\"{orig = }\")\n",
...@@ -672,12 +707,16 @@ ...@@ -672,12 +707,16 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 13, "execution_count": null,
"id": "4c392292-2d9f-4301-afb5-019fde069a1e", "id": "4c392292-2d9f-4301-afb5-019fde069a1e",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"out = otree.nearby_points(Record(179.5, -43.1, datetime(1900, 1, 14, 13)), dist=200, t_dist=timedelta(days=3))\n", "out = otree.nearby_points(\n",
" Record(179.5, -43.1, datetime(1900, 1, 14, 13)),\n",
" dist=200,\n",
" t_dist=timedelta(days=3),\n",
")\n",
"for o in out:\n", "for o in out:\n",
" print(o)" " print(o)"
] ]
...@@ -685,7 +724,7 @@ ...@@ -685,7 +724,7 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "GeoSpatialTools", "display_name": "geospatialtools",
"language": "python", "language": "python",
"name": "geospatialtools" "name": "geospatialtools"
}, },
...@@ -699,7 +738,7 @@ ...@@ -699,7 +738,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.12.7" "version": "3.11.11"
} }
}, },
"nbformat": 4, "nbformat": 4,
......
...@@ -33,22 +33,19 @@ classifiers = [ ...@@ -33,22 +33,19 @@ classifiers = [
[project.optional-dependencies] [project.optional-dependencies]
notebooks = ["ipykernel", "polars"] notebooks = ["ipykernel", "polars"]
test = ["pytest>=8.3.4"] test = ["pytest"]
docs = [ docs = ["sphinx", "sphinx-autodoc-typehints", "sphinx-rtd-theme"]
"sphinx>=8.2.1",
"sphinx-autodoc-typehints>=3.1.0",
"sphinx-rtd-theme>=3.0.2",
]
all = [ all = [
"ipykernel", "ipykernel",
"polars", "polars",
"pytest", "pytest",
"sphinx>=8.2.1", "sphinx",
"sphinx-autodoc-typehints>=3.1.0", "sphinx-autodoc-typehints",
"sphinx-rtd-theme>=3.0.2", "sphinx-rtd-theme",
] ]
[tool.ruff] [tool.ruff]
src = ["geospatialtools"]
line-length = 80 line-length = 80
indent-width = 4 indent-width = 4
target-version = "py311" target-version = "py311"
...@@ -75,6 +72,11 @@ select = [ ...@@ -75,6 +72,11 @@ select = [
"W", # pycodestyle warnings "W", # pycodestyle warnings
] ]
[tool.ruff.lint.per-file-ignores]
"docs/*.py" = ["D100", "D101", "D102", "D103"]
"test/**/*test*.py" = ["D100", "D101", "D102", "D103", "N802", "S101", "S311"]
"notebooks/*.ipynb" = ["D100", "D101", "D102", "D103", "N802", "S101", "S311"]
[tool.ruff.format] [tool.ruff.format]
quote-style = "double" # Like Black, use double quotes for strings. quote-style = "double" # Like Black, use double quotes for strings.
indent-style = "space" # Like Black, indent with spaces, rather than tabs. indent-style = "space" # Like Black, indent with spaces, rather than tabs.
......
...@@ -2,7 +2,7 @@ import unittest ...@@ -2,7 +2,7 @@ import unittest
import random import random
from numpy import min, argmin from numpy import min, argmin
from GeoSpatialTools import haversine, KDTree, Record from GeoSpatialTools import KDTree, Record
class TestKDTree(unittest.TestCase): class TestKDTree(unittest.TestCase):
...@@ -87,7 +87,7 @@ class TestKDTree(unittest.TestCase): ...@@ -87,7 +87,7 @@ class TestKDTree(unittest.TestCase):
kt.insert(Record(45, -21, uid="1")) kt.insert(Record(45, -21, uid="1"))
kt.insert(Record(45, -21, uid="2")) kt.insert(Record(45, -21, uid="2"))
r, d = kt.query(Record(44, -21, uid="3")) r, _ = kt.query(Record(44, -21, uid="3"))
assert len(r) == 2 assert len(r) == 2
def test_wrap(self): def test_wrap(self):
......
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