Skip to content

GitLab

  • Projects
  • Groups
  • Snippets
  • Help
    • Loading...
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
    • Contribute to GitLab
  • Sign in
G GeoTrees
  • Project overview
    • Project overview
    • Details
    • Activity
    • Releases
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
  • Issues 3
    • Issues 3
    • List
    • Boards
    • Labels
    • Service Desk
    • Milestones
  • Merge requests 0
    • Merge requests 0
  • CI/CD
    • CI/CD
    • Pipelines
    • Jobs
    • Schedules
  • Operations
    • Operations
    • Metrics
    • Incidents
    • Environments
  • Packages & Registries
    • Packages & Registries
    • Package Registry
  • Analytics
    • Analytics
    • CI/CD
    • Repository
    • Value Stream
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Members
    • Members
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar
  • NOCSurfaceProcesses
  • GeoTrees
  • Merge requests
  • !27

Merged
Created 2 months ago by Joseph Siddons@josiddOwner

Resolve "Improve documentation"

  • Overview 2
  • Commits 25
  • Pipelines 3
  • Changes 30
1 unresolved thread

Closes #10 (closed)

Edited 2 months ago by Joseph Siddons
  • Joseph Siddons @josidd changed milestone to %Version 1 2 months ago

    changed milestone to %Version 1

  • Joseph Siddons @josidd added DOCS label 2 months ago

    added DOCS label

  • Joseph Siddons @josidd added 1 commit 2 months ago

    added 1 commit

    • 013d3e72 - docs: remove header from module docstring

    Compare with previous version

  • Joseph Siddons @josidd added 2 commits 2 months ago

    added 2 commits

    • 8302ff2f - docs: add sections for quadtree and record classes
    • b5338b62 - docs: add more octtree documentation

    Compare with previous version

  • Joseph Siddons @josidd marked this merge request as draft 2 months ago

    marked this merge request as draft

  • Joseph Siddons @josidd added 2 commits 2 months ago

    added 2 commits

    • 179abd21 - refactor!: move record and shape objects to separate modules
    • a80ee486 - chore: update changelog

    Compare with previous version

  • Joseph Siddons @josidd added 1 commit 2 months ago

    added 1 commit

    • 165874bd - docs: add docstring for record module

    Compare with previous version

  • Joseph Siddons @josidd added 7 commits 2 months ago

    added 7 commits

    • 81b58c7e - docs: correct class names in docstrings
    • 2133146b - docs: correct imports on readme
    • 58486683 - docs: change syntax highlighting scheme
    • c848f1ab - docs: correct imports and re-run notebook
    • edda0afd - docs: add section title to docstring
    • 5b169c44 - docs: add clarifying comment
    • 2bd97b5c - fix: correct imports

    Compare with previous version

    Toggle commit list
  • Joseph Siddons @josidd added 1 commit 2 months ago

    added 1 commit

    • b1ac976b - docs: improve docstrings for find_nearest

    Compare with previous version

  • Joseph Siddons @josidd added 4 commits 2 months ago

    added 4 commits

    • 4ae12b16 - refactor!: remove SpaceTimeRecords class, use list[SpaceTimeRecord]
    • ff46acb1 - docs: improve QuadTree method docstrings
    • b006a26a - docs: complete documentation
    • 67b9e5d4 - chore: update changelog

    Compare with previous version

    Toggle commit list
    • Joseph Siddons
      Joseph Siddons @josidd · 2 months ago
      Owner

      @ricorne : please have a look at docs/Documentation.pdf and let me know if you have any suggestions.

    • Collapse replies
    • Joseph Siddons
      Joseph Siddons @josidd · 2 months ago
      Owner

      https://git.noc.ac.uk/nocsurfaceprocesses/geospatialtools/-/blob/0bb2e0e603aef49e789188a1c7a8097a22fb8421/docs/Documentation.pdf

      edit: update file to latest commit

      Edited by Joseph Siddons 1 month ago
    • Please register or sign in to reply
  • Joseph Siddons @josidd requested review from @ricorne 2 months ago

    requested review from @ricorne

  • Joseph Siddons @josidd marked this merge request as ready 2 months ago

    marked this merge request as ready

  • Joseph Siddons @josidd added 25 commits 1 month ago

    added 25 commits

    • 67b9e5d4...e606e155 - 23 commits from branch nocsurfaceprocesses:main
    • 8e9bae21 - Merge branch 'main' into 10-improve-documentation
    • 611ebfa6 - Merge "main" into "10-improve-documentation"

    Compare with previous version

  • Joseph Siddons @josidd added 1 commit 1 month ago

    added 1 commit

    • bd0d5c29 - ci: pre-commit fixes

    Compare with previous version

  • Joseph Siddons @josidd added 1 commit 1 month ago

    added 1 commit

    • 0c92925a - docs: add pre-commit instructions to CONTRIBUTING

    Compare with previous version

  • Joseph Siddons @josidd merged 2 days ago

    merged

  • Joseph Siddons @josidd mentioned in commit 786a9658 2 days ago

    mentioned in commit 786a9658

  • You're only seeing other activity in the feed. To add a comment, switch to one of the following options.
Please register or sign in to reply
Compare
  • version 10
    bd0d5c29
    1 month ago

  • version 9
    611ebfa6
    1 month ago

  • version 8
    67b9e5d4
    2 months ago

  • version 7
    b1ac976b
    2 months ago

  • version 6
    2bd97b5c
    2 months ago

  • version 5
    165874bd
    2 months ago

  • version 4
    a80ee486
    2 months ago

  • version 3
    b5338b62
    2 months ago

  • version 2
    013d3e72
    2 months ago

  • version 1
    eb85c872
    2 months ago

  • main (base)

and
  • latest version
    0c92925a
    25 commits, 1 month ago

  • version 10
    bd0d5c29
    24 commits, 1 month ago

  • version 9
    611ebfa6
    23 commits, 1 month ago

  • version 8
    67b9e5d4
    21 commits, 2 months ago

  • version 7
    b1ac976b
    17 commits, 2 months ago

  • version 6
    2bd97b5c
    16 commits, 2 months ago

  • version 5
    165874bd
    9 commits, 2 months ago

  • version 4
    a80ee486
    8 commits, 2 months ago

  • version 3
    b5338b62
    6 commits, 2 months ago

  • version 2
    013d3e72
    4 commits, 2 months ago

  • version 1
    eb85c872
    3 commits, 2 months ago

10 files
+ 698
- 667
Show latest version
    File browser
    Compare changes
GeoSpat‎ialTools‎
__ini‎t__.py‎ +3 -1
kdtr‎ee.py‎ +1 -3
octtr‎ee.py‎ +4 -378
quadt‎ree.py‎ +3 -280
reco‎rd.py‎ +172 -0
shap‎e.py‎ +503 -0
te‎st‎
test_kd‎tree.py‎ +1 -1
test_oc‎ttree.py‎ +3 -3
test_qua‎dtree.py‎ +3 -1
CHANG‎ES.md‎ +5 -0
GeoSpatialTools/__init__.py
+ 3
- 1
  • View file @ a80ee486

  • Edit in single-file editor

  • Edit in Web IDE


Files with large changes are collapsed by default.

Show all unchanged lines Show 20 lines
from .neighbours import find_nearest
from .distance_metrics import haversine
from .great_circle import GreatCircle
from .quadtree import Ellipse, QuadTree, Record, Rectangle
from .record import Record
from .quadtree import QuadTree
from .shape import Ellipse, Rectangle
from .kdtree import KDTree
__all__ = [
Show 20 lines Show all unchanged lines
GeoSpatialTools/kdtree.py
+ 1
- 3
  • View file @ a80ee486

  • Edit in single-file editor

  • Edit in Web IDE


Files with large changes are collapsed by default.

"""
KDTree
------
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.
Show 20 lines Show all unchanged lines Show 20 lines
of the earth is accounted for.
"""
from . import Record
from .record import Record
from numpy import inf
from typing import List, Optional, Tuple
Show 20 lines Show all unchanged lines
GeoSpatialTools/octtree.py
+ 4
- 378
  • View file @ a80ee486

  • Edit in single-file editor

  • Edit in Web IDE


Files with large changes are collapsed by default.

Show all unchanged lines Show 20 lines
neighbours.
"""
from dataclasses import dataclass
from typing import List, Optional
from typing import Optional
import datetime
from .distance_metrics import haversine, destination
from .utils import LatitudeError, DateWarning
from math import degrees, sqrt
from warnings import warn
class SpaceTimeRecord:
"""
ICOADS Record class.
This is a simple instance of an ICOARDS record, it requires position and
temporal data. It can optionally include a UID and extra data.
The temporal component was designed to use `datetime` values, however all
methods will work with numeric datetime information - 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.
Equality is checked only on the required fields + UID if it is specified.
Parameters
----------
lon : float
Horizontal coordinate (longitude).
lat : float
Vertical coordinate (latitude).
datetime : datetime.datetime
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.
uid : str | None
Unique Identifier.
fix_lon : bool
Force longitude to -180, 180
**data
Additional data passed to the SpaceTimeRecord for use by other functions
or classes.
"""
def __init__(
self,
lon: float,
lat: float,
datetime: datetime.datetime,
uid: Optional[str] = None,
fix_lon: bool = True,
**data,
) -> None:
self.lon = lon
if fix_lon:
# Move lon to -180, 180
self.lon = ((self.lon + 540) % 360) - 180
if lat < -90 or lat > 90:
raise LatitudeError(
"Expected latitude value to be between -90 and 90 degrees"
)
self.lat = lat
self.datetime = datetime
self.uid = uid
for var, val in data.items():
setattr(self, var, val)
return None
def __str__(self) -> str:
return (
f"SpaceTimeRecord(x = {self.lon}, y = {self.lat}, "
+ f"datetime = {self.datetime}, uid = {self.uid})"
)
def __eq__(self, other: object) -> bool:
if not isinstance(other, SpaceTimeRecord):
return False
if self.uid and other.uid:
return self.uid == other.uid
return (
self.lon == other.lon
and self.lat == other.lat
and self.datetime == other.datetime
and (not (self.uid or other.uid) or self.uid == other.uid)
)
def distance(self, other: object) -> float:
"""
Compute the Haversine distance to another SpaceTimeRecord.
Only computes spatial distance.
"""
if not isinstance(other, SpaceTimeRecord):
raise TypeError("Argument other must be an instance of Record")
return haversine(self.lon, self.lat, other.lon, other.lat)
class SpaceTimeRecords(List[SpaceTimeRecord]):
"""List of SpaceTimeRecords"""
@dataclass
class SpaceTimeRectangle:
"""
A simple Space Time SpaceTimeRectangle class.
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.
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.
The temporal components are defined in the same way as the spatial
components, that is that the `datetime` component (t) is the "centre", and
the time extent (dt) is the full time range of the box.
Parameters
----------
west : float
Western boundary of the Rectangle
east : float
Eastern boundary of the Rectangle
south : float
Southern boundary of the Rectangle
north : float
Northern boundary of the Rectangle
start : datetime.datetime
Start datetime of the Rectangle
end : datetime.datetime
End datetime of the Rectangle
"""
west: float
east: float
south: float
north: float
start: datetime.datetime
end: datetime.datetime
def __post_init__(self):
if self.east > 180 or self.east < -180:
self.east = ((self.east + 540) % 360) - 180
if self.west > 180 or self.west < -180:
self.west = ((self.west + 540) % 360) - 180
if self.north > 90 or self.south < -90:
raise LatitudeError(
"Latitude bounds are out of bounds. "
+ f"{self.north = }, {self.south = }"
)
if self.end < self.start:
warn("End date is before start date. Swapping", DateWarning)
self.start, self.end = self.end, self.start
@property
def lat_range(self) -> float:
"""Latitude range of the Rectangle"""
return self.north - self.south
@property
def lat(self) -> float:
"""Centre latitude of the Rectangle"""
return self.south + self.lat_range / 2
@property
def lon_range(self) -> float:
"""Longitude range of the Rectangle"""
if self.east < self.west:
return self.east - self.west + 360
return self.east - self.west
@property
def lon(self) -> float:
"""Centre longitude of the Rectangle"""
lon = self.west + self.lon_range / 2
return ((lon + 540) % 360) - 180
@property
def edge_dist(self) -> float:
"""Approximate maximum distance from the centre to an edge"""
corner_dist = max(
haversine(self.lon, self.lat, self.east, self.north),
haversine(self.lon, self.lat, self.east, self.south),
)
if self.north * self.south < 0:
corner_dist = max(
corner_dist,
haversine(self.lon, self.lat, self.east, 0),
)
return corner_dist
@property
def time_range(self) -> datetime.timedelta:
"""The time extent of the Rectangle"""
return self.end - self.start
@property
def centre_datetime(self) -> datetime.datetime:
"""The midpoint time of the Rectangle"""
return self.start + (self.end - self.start) / 2
def _test_east_west(self, lon: float) -> bool:
if self.lon_range >= 360:
# Rectangle encircles earth
return True
if self.east > self.lon and self.west < self.lon:
return lon <= self.east and lon >= self.west
if self.east < self.lon:
return not (lon > self.east and lon < self.west)
if self.west > self.lon:
return not (lon < self.east and lon > self.west)
return False
def _test_north_south(self, lat: float) -> bool:
return lat <= self.north and lat >= self.south
def contains(self, point: SpaceTimeRecord) -> bool:
"""Test if a point is contained within the SpaceTimeRectangle"""
if point.datetime > self.end or point.datetime < self.start:
return False
return self._test_north_south(point.lat) and self._test_east_west(
point.lon
)
def intersects(self, other: object) -> bool:
"""Test if another Rectangle object intersects this Rectangle"""
if not isinstance(other, SpaceTimeRectangle):
raise TypeError(
f"other must be a Rectangle class, got {type(other)}"
)
if other.end < self.start or other.start > self.end:
# Not in the same time range
return False
if other.south > self.north:
# Other is fully north of self
return False
if other.north < self.south:
# Other is fully south of self
return False
# Handle east / west edges
return (
self._test_east_west(other.west)
or self._test_east_west(other.east)
# Fully contained within other
or (
other._test_east_west(self.west)
and other._test_east_west(self.east)
)
)
def nearby(
self,
point: SpaceTimeRecord,
dist: float,
t_dist: datetime.timedelta,
) -> bool:
"""
Check if point is nearby the Rectangle
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.
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.
The primary use-case of this method is for querying an OctTree for
nearby Records.
Parameters
----------
point : SpaceTimeRecord
dist : float,
t_dist : datetime.timedelta
Returns
-------
bool : True if the point is <= dist + max(dist(centre, corners))
"""
if (
point.datetime - t_dist > self.end
or point.datetime + t_dist < self.start
):
return False
# QUESTION: Is this sufficient? Possibly it is overkill
return (
haversine(self.lon, self.lat, point.lon, point.lat)
<= dist + self.edge_dist
)
class SpaceTimeEllipse:
"""
A simple Ellipse Class for an ellipse on the surface of a sphere.
Parameters
----------
lon : float
Horizontal centre of the ellipse
lat : float
Vertical centre of the ellipse
a : float
Length of the semi-major axis
b : float
Length of the semi-minor axis
theta : float
Angle of the semi-major axis from horizontal anti-clockwise in radians
start : datetime.datetime
Start date of the Ellipse
end : datetime.datetime
Send date of the Ellipse
"""
def __init__(
self,
lon: float,
lat: float,
a: float,
b: float,
theta: float,
start: datetime.datetime,
end: datetime.datetime,
) -> None:
self.a = a
self.b = b
self.lon = lon
if self.lon > 180:
self.lon = ((self.lon + 540) % 360) - 180
self.lat = lat
self.start = start
self.end = end
if self.end < self.start:
warn("End date is before start date. Swapping")
self.start, self.end = self.end, self.start
# theta is anti-clockwise angle from horizontal in radians
self.theta = theta
# bearing is angle clockwise from north in degrees
self.bearing = (90 - degrees(self.theta)) % 360
a2 = self.a * self.a
b2 = self.b * self.b
self.c = sqrt(a2 - b2)
self.p1_lon, self.p1_lat = destination(
self.lon,
self.lat,
self.bearing,
self.c,
)
self.p2_lon, self.p2_lat = destination(
self.lon,
self.lat,
(self.bearing - 180) % 360,
self.c,
)
def contains(self, point: SpaceTimeRecord) -> bool:
"""Test if a point is contained within the Ellipse"""
if point.datetime > self.end or point.datetime < self.start:
return False
return (
haversine(self.p1_lon, self.p1_lat, point.lon, point.lat)
+ haversine(self.p2_lon, self.p2_lat, point.lon, point.lat)
) <= 2 * self.a
def nearby_rect(self, rect: SpaceTimeRectangle) -> bool:
"""Test if a rectangle is near to the Ellipse"""
if rect.start > self.end or rect.end < self.start:
return False
# TODO: Check corners, and 0 lat
return (
haversine(self.p1_lon, self.p1_lat, rect.lon, rect.lat)
<= rect.edge_dist + self.a
and haversine(self.p2_lon, self.p2_lat, rect.lon, rect.lat)
<= rect.edge_dist + self.a
)
from .distance_metrics import haversine
from .record import SpaceTimeRecord, SpaceTimeRecords
from .shape import SpaceTimeEllipse, SpaceTimeRectangle
class OctTree:
Show 20 lines Show all unchanged lines
GeoSpatialTools/quadtree.py
+ 3
- 280
  • View file @ a80ee486

  • Edit in single-file editor

  • Edit in Web IDE


GeoSpatialTools/record.py
+ 172
- 0
  • View file @ a80ee486

  • Edit in single-file editor

  • Edit in Web IDE

GeoSpatialTools/shape.py
+ 503
- 0
  • View file @ a80ee486

  • Edit in single-file editor

  • Edit in Web IDE

test/test_kdtree.py
+ 1
- 1
  • View file @ a80ee486

  • Edit in single-file editor

  • Edit in Web IDE


test/test_octtree.py
+ 3
- 3
  • View file @ a80ee486

  • Edit in single-file editor

  • Edit in Web IDE


test/test_quadtree.py
+ 3
- 1
  • View file @ a80ee486

  • Edit in single-file editor

  • Edit in Web IDE


CHANGES.md
+ 5
- 0
  • View file @ a80ee486

  • Edit in single-file editor

  • Edit in Web IDE


Assignee
Joseph Siddons's avatar
Joseph Siddons
@josidd
Assign to
Reviewer
ricorne's avatar
@ricorne
Request review from
Version 1
Milestone
Version 1
Assign milestone
None
Time tracking
No estimate or time spent
1
Labels
DOCS
Assign labels
  • No matching results
  • Manage project labels
Lock merge request
Unlocked
2
2 participants
user avatar
user avatar
Reference: nocsurfaceprocesses/geotrees!27
Source branch: 10-improve-documentation

    0 pending comments