Spatial Calculations

The spatial module provides comprehensive geospatial calculations for KML elements, including distance calculations, bearing computations, and coordinate operations.

Overview

The spatial module implements a Protocol-based design that allows any object with coordinates to participate in spatial calculations. This provides clean separation of concerns and extensibility.

Key Features

  • Multiple distance calculation strategies - Haversine (default), Vincenty (high precision), and Euclidean (fast approximation)

  • Support for multiple distance units - Kilometers, meters, miles, nautical miles, feet, and yards

  • Protocol-based design - Any object implementing HasCoordinates can use spatial operations

  • LRU caching - Automatic caching of repeated calculations for performance

  • Bulk operations - Efficient batch distance calculations

Quick Example

from kmlorm.models import Placemark
from kmlorm.spatial import DistanceUnit

# Create two placemarks
nyc = Placemark(name="NYC", coordinates=(-74.006, 40.7128))
london = Placemark(name="London", coordinates=(-0.1276, 51.5074))

# Calculate distance (default: kilometers)
distance_km = nyc.distance_to(london)
print(f"Distance: {distance_km:.1f} km")  # Distance: 5570.2 km

# Calculate in different units
distance_miles = nyc.distance_to(london, unit=DistanceUnit.MILES)
print(f"Distance: {distance_miles:.1f} miles")  # Distance: 3461.0 miles

# Calculate bearing
bearing = nyc.bearing_to(london)
print(f"Bearing: {bearing:.1f}°")  # Bearing: 51.2°

# Find midpoint
midpoint = nyc.midpoint_to(london)
print(f"Midpoint: ({midpoint.longitude:.2f}, {midpoint.latitude:.2f})")
# Midpoint: (-41.29, 52.37)

Core Components

HasCoordinates Protocol

The HasCoordinates protocol defines the interface for objects that can provide coordinates:

from typing import Protocol, Optional
from kmlorm.models.point import Coordinate

class HasCoordinates(Protocol):
    """Protocol for objects that can provide coordinates."""

    def get_coordinates(self) -> Optional[Coordinate]:
        """Return the coordinate representation of this object."""
        ...

Any object implementing this protocol can participate in spatial calculations. The following classes implement HasCoordinates:

  • Coordinate

  • Point

  • Placemark

Distance Units

class kmlorm.spatial.DistanceUnit(Enum)[source]

Enumeration of available distance units with conversion factors from kilometers.

KILOMETERS

Kilometers (base unit, factor = 1.0)

METERS

Meters (factor = 1000.0)

MILES

Statute miles (factor = 0.621371)

NAUTICAL_MILES

Nautical miles (factor = 0.539957)

FEET

Feet (factor = 3280.84)

YARDS

Yards (factor = 1093.61)

Example usage:

from kmlorm.spatial import DistanceUnit

# Calculate distance in different units
distance_km = point1.distance_to(point2)
distance_m = point1.distance_to(point2, unit=DistanceUnit.METERS)
distance_mi = point1.distance_to(point2, unit=DistanceUnit.MILES)

Spatial Calculations

class kmlorm.spatial.SpatialCalculations[source]

Core class providing static methods for spatial calculations between geographic coordinates. All calculations use the WGS84 ellipsoid model and assume coordinates in decimal degrees.

classmethod distance_between(from_obj: HasCoordinates, to_obj: HasCoordinates, unit: DistanceUnit = DistanceUnit.KILOMETERS) float | None[source]

Calculate the distance between two objects with coordinates.

Parameters:
  • from_obj – Source object with coordinates

  • to_obj – Destination object with coordinates

  • unit – Unit for distance measurement (default: kilometers)

Returns:

Distance in specified units, or None if coordinates unavailable

Example:

from kmlorm.spatial.calculations import SpatialCalculations
from kmlorm.models.point import Coordinate

coord1 = Coordinate(longitude=-74.006, latitude=40.7128)
coord2 = Coordinate(longitude=-0.1276, latitude=51.5074)

distance = SpatialCalculations.distance_between(coord1, coord2)
print(f"Distance: {distance:.1f} km")
classmethod bearing_between(from_obj: HasCoordinates, to_obj: HasCoordinates) float | None[source]

Calculate the initial bearing (azimuth) between two objects.

Parameters:
  • from_obj – Source object with coordinates

  • to_obj – Destination object with coordinates

Returns:

Initial bearing in degrees (0-360), where 0° = North, 90° = East, etc.

Example:

bearing = SpatialCalculations.bearing_between(coord1, coord2)
print(f"Bearing: {bearing:.1f}°")
classmethod midpoint(obj1: HasCoordinates, obj2: HasCoordinates) Coordinate | None[source]

Find the geographic midpoint between two objects.

Parameters:
  • obj1 – First object with coordinates

  • obj2 – Second object with coordinates

Returns:

Coordinate representing the midpoint, or None if calculation fails

classmethod distances_to_many(from_obj: HasCoordinates, to_objects: List[HasCoordinates], unit: DistanceUnit = DistanceUnit.KILOMETERS) List[float | None][source]

Calculate distances from one object to many others efficiently.

Parameters:
  • from_obj – Source object with coordinates

  • to_objects – List of destination objects

  • unit – Unit for distance measurements

Returns:

List of distances (None for objects without coordinates)

Example:

center = Coordinate(longitude=0, latitude=0)
targets = [coord1, coord2, coord3, coord4]

distances = SpatialCalculations.distances_to_many(center, targets)
for i, dist in enumerate(distances):
    if dist is not None:
        print(f"Distance to target {i}: {dist:.1f} km")

Distance Calculation Strategies

The spatial module provides multiple strategies for distance calculation, each with different trade-offs between accuracy and performance.

class kmlorm.spatial.HaversineStrategy

Great circle distance using the Haversine formula. This is the default strategy, providing a good balance of speed and accuracy.

  • Accuracy: ±0.5% for most distances

  • Performance: Fast (O(1) with simple trigonometric operations)

  • Best for: General purpose distance calculations

class kmlorm.spatial.VincentyStrategy

Vincenty’s formulae for accurate distance on an oblate spheroid (WGS84 ellipsoid). More accurate than Haversine but significantly slower.

  • Accuracy: ±0.5mm for distances up to ~20,000 km

  • Performance: Slower (iterative algorithm)

  • Best for: High-precision applications requiring maximum accuracy

class kmlorm.spatial.EuclideanApproximation

Fast Euclidean approximation using equirectangular projection. Very fast but only accurate for small distances.

  • Accuracy: Good for distances <100km, decreases with distance and latitude

  • Performance: Very fast (O(1) with minimal operations)

  • Best for: Quick approximations when speed is critical and distances are small

class kmlorm.spatial.AdaptiveStrategy

Adaptive strategy that automatically selects the best algorithm based on distance and requirements. Uses Euclidean for very small distances (<50km), Haversine for medium distances, and optionally Vincenty for long distances when high accuracy is requested.

Usage Examples

Basic Distance Calculations

from kmlorm.models import Placemark, Point
from kmlorm.models.point import Coordinate

# Distance between Coordinate objects
coord1 = Coordinate(longitude=-74.006, latitude=40.7128)  # NYC
coord2 = Coordinate(longitude=-0.1276, latitude=51.5074)   # London
distance = coord1.distance_to(coord2)

# Distance between Points
point1 = Point(coordinates=(-74.006, 40.7128))
point2 = Point(coordinates=(-0.1276, 51.5074))
distance = point1.distance_to(point2)

# Distance between Placemarks
place1 = Placemark(name="NYC", coordinates=(-74.006, 40.7128))
place2 = Placemark(name="London", coordinates=(-0.1276, 51.5074))
distance = place1.distance_to(place2)

# Mixed types - all work together
distance = coord1.distance_to(place2)
distance = point1.distance_to(coord2)

Working with Different Units

from kmlorm.spatial import DistanceUnit

# Default is kilometers
km = place1.distance_to(place2)

# Other units
meters = place1.distance_to(place2, unit=DistanceUnit.METERS)
miles = place1.distance_to(place2, unit=DistanceUnit.MILES)
nautical = place1.distance_to(place2, unit=DistanceUnit.NAUTICAL_MILES)
feet = place1.distance_to(place2, unit=DistanceUnit.FEET)
yards = place1.distance_to(place2, unit=DistanceUnit.YARDS)

print(f"Distance: {km:.1f} km = {miles:.1f} mi = {nautical:.1f} nm")

Bearing and Navigation

# Calculate bearing (initial heading)
bearing = place1.bearing_to(place2)
print(f"Initial bearing from NYC to London: {bearing:.1f}°")

# Bearing interpretation
if bearing < 22.5 or bearing >= 337.5:
    direction = "North"
elif bearing < 67.5:
    direction = "Northeast"
elif bearing < 112.5:
    direction = "East"
elif bearing < 157.5:
    direction = "Southeast"
elif bearing < 202.5:
    direction = "South"
elif bearing < 247.5:
    direction = "Southwest"
elif bearing < 292.5:
    direction = "West"
else:
    direction = "Northwest"

print(f"Head {direction} ({bearing:.1f}°)")

Finding Midpoints

# Find geographic midpoint
midpoint = place1.midpoint_to(place2)

print(f"Midpoint between NYC and London:")
print(f"  Longitude: {midpoint.longitude:.4f}")
print(f"  Latitude: {midpoint.latitude:.4f}")

# Create a new placemark at the midpoint
midpoint_place = Placemark(
    name="Atlantic Midpoint",
    coordinates=(midpoint.longitude, midpoint.latitude)
)

Bulk Distance Operations

from kmlorm.spatial.calculations import SpatialCalculations

# Efficient bulk distance calculations
center = Coordinate(longitude=0, latitude=0)

# Many target locations
targets = [
    Placemark(name="North", coordinates=(0, 10)),
    Placemark(name="South", coordinates=(0, -10)),
    Placemark(name="East", coordinates=(10, 0)),
    Placemark(name="West", coordinates=(-10, 0)),
    Placemark(name="Northeast", coordinates=(10, 10)),
]

# Calculate all distances at once
distances = SpatialCalculations.distances_to_many(center, targets)

for target, distance in zip(targets, distances):
    if distance is not None:
        print(f"{target.name}: {distance:.1f} km")

Integration with QuerySets

The QuerySet’s near() method uses the spatial calculations internally:

from kmlorm.parsers import KMLFile

# Load KML file
kml = KMLFile.from_file("stores.kml")

# Find all stores within 50km of a location
center_lat, center_lon = 40.7128, -74.006  # NYC

nearby_stores = kml.placemarks.near(
    longitude=center_lon,
    latitude=center_lat,
    radius_km=50
)

for store in nearby_stores:
    # Each store has distance methods available
    distance = store.distance_to((center_lon, center_lat))
    bearing = store.bearing_to((center_lon, center_lat))
    print(f"{store.name}: {distance:.1f} km at {bearing:.0f}°")

Edge Cases and Limitations

Date Line Crossing

The Haversine and Vincenty strategies correctly handle date line crossing:

# Points on opposite sides of the International Date Line
west_of_date_line = Coordinate(longitude=179.5, latitude=0)
east_of_date_line = Coordinate(longitude=-179.5, latitude=0)

# Correctly calculates ~111 km, not ~39,000 km
distance = west_of_date_line.distance_to(east_of_date_line)

Polar Regions

All strategies handle polar calculations correctly:

north_pole = Coordinate(longitude=0, latitude=90)
south_pole = Coordinate(longitude=0, latitude=-90)

# Pole to pole distance
distance = north_pole.distance_to(south_pole)  # ~20,004 km

Coordinate Validation

Invalid coordinates are handled gracefully:

from kmlorm.models import Placemark

# Placemark without coordinates
place_no_coords = Placemark(name="Unknown Location")

# Returns None for objects without valid coordinates
distance = place_no_coords.distance_to(north_pole)  # None

Performance Considerations

Caching

The spatial module uses LRU caching for repeated calculations:

# These repeated calculations are cached automatically
for i in range(1000):
    distance = place1.distance_to(place2)  # Cached after first calculation

Strategy Selection

Choose the appropriate strategy based on your needs:

from kmlorm.spatial.strategies import HaversineStrategy, VincentyStrategy

# For general use (default)
strategy = HaversineStrategy()

# For high precision requirements
strategy = VincentyStrategy()

# Use with SpatialCalculations
from kmlorm.spatial.calculations import SpatialCalculations

# The strategy can be selected internally based on requirements

Typical Use Cases

For typical KML files with hundreds of placemarks, the default Haversine strategy provides excellent performance and accuracy. The spatial calculations are optimized for this common use case.

API Reference

Spatial calculations package for KML ORM.

This package provides spatial calculations and utilities for geometric operations on KML elements including distance, bearing, and coordinate transformations.

Key Components:
  • calculations: Core spatial calculation functions

  • exceptions: Spatial-specific exceptions

  • strategies: Distance calculation strategies

  • constants: WGS84 and other spatial constants

Examples

>>> from kmlorm.spatial.calculations import SpatialCalculations, DistanceUnit
>>> from kmlorm.models.point import Coordinate
>>>
>>> # Calculate distance between two coordinates
>>> coord1 = Coordinate(longitude=-74.006, latitude=40.7128)  # NYC
>>> coord2 = Coordinate(longitude=-0.1276, latitude=51.5074)  # London
>>> distance = SpatialCalculations.distance_between(coord1, coord2)
>>> print(f"Distance: {distance:.1f} km")
class kmlorm.spatial.SpatialCalculations[source]

Bases: object

Spatial calculation utilities following WGS84 datum.

All calculations assume: - WGS84 ellipsoid (a=6378137.0m, f=1/298.257223563) - Coordinates in decimal degrees - Longitude: -180 to 180 - Latitude: -90 to 90

Mathematical Accuracy: - Haversine formula: ±0.5% for most distances - Good for distances up to ~20,000 km - Assumes spherical Earth (mean radius 6371.0088 km)

classmethod bearing_between(cls, from_obj, to_obj)[source]

Calculate the initial bearing from one object to another.

Parameters:
Return type:

Optional[float]

Returns:

Initial bearing in degrees (0-360), or None if coordinates unavailable 0° = North, 90° = East, 180° = South, 270° = West

Raises:

SpatialCalculationError – If calculation fails

Examples

>>> coord1 = Coordinate(longitude=0, latitude=0)
>>> coord2 = Coordinate(longitude=1, latitude=0)  # Due east
>>> bearing = SpatialCalculations.bearing_between(coord1, coord2)
>>> print(f"Bearing: {bearing:.1f}°")  # Should be ~90°
classmethod bounding_box(cls, objects)[source]

Calculate minimum bounding rectangle for a set of objects.

Parameters:

objects (List[Union[HasCoordinates, Tuple[float, float], List[float]]]) – List of objects with coordinates

Return type:

Optional[Tuple[float, float, float, float]]

Returns:

Tuple of (min_lon, min_lat, max_lon, max_lat) or None if no valid coordinates

Examples

>>> points = [
...     Coordinate(longitude=-1, latitude=-1),
...     Coordinate(longitude=1, latitude=1),
...     Coordinate(longitude=0, latitude=2),
... ]
>>> bbox = SpatialCalculations.bounding_box(points)
>>> print(f"Bounding box: {bbox}")  # (-1, -1, 1, 2)
classmethod distance_between(cls, from_obj, to_obj, unit=DistanceUnit.KILOMETERS)[source]

Calculate distance between two objects with coordinates.

Parameters:
Return type:

Optional[float]

Returns:

Distance in specified units, or None if coordinates unavailable

Raises:

Examples

>>> from kmlorm.models.point import Coordinate
>>> coord1 = Coordinate(longitude=0, latitude=0)
>>> coord2 = Coordinate(longitude=1, latitude=1)
>>> distance = SpatialCalculations.distance_between(coord1, coord2)
>>> print(f"Distance: {distance:.2f} km")
>>> # Using tuples
>>> distance = SpatialCalculations.distance_between((0, 0), (1, 1))
>>> # Different units
>>> distance_miles = SpatialCalculations.distance_between(
...     coord1, coord2, unit=DistanceUnit.MILES
... )
classmethod distances_to_many(cls, from_obj, to_objects, unit=DistanceUnit.KILOMETERS)[source]

Calculate distances from one object to many others efficiently.

This is more efficient than calling distance_between() repeatedly because it extracts the source coordinates once and reuses the calculation.

Parameters:
Return type:

List[Optional[float]]

Returns:

List of distances in specified units (None for objects without coordinates)

Time Complexity: O(n) where n = len(to_objects) Space Complexity: O(n) for result list

Examples

>>> center = Coordinate(longitude=0, latitude=0)
>>> points = [
...     Coordinate(longitude=1, latitude=0),
...     Coordinate(longitude=0, latitude=1),
...     Coordinate(longitude=-1, latitude=0),
... ]
>>> distances = SpatialCalculations.distances_to_many(center, points)
classmethod interpolate(cls, start, end, fraction)[source]

Find a point along the great circle path between two coordinates.

Parameters:
Return type:

Optional[Coordinate]

Returns:

Coordinate at the specified fraction along the path, or None if coordinates unavailable

Raises:

ValueError – If fraction is not in range [0, 1]

Examples

>>> start = Coordinate(longitude=0, latitude=0)
>>> end = Coordinate(longitude=10, latitude=10)
>>> quarter_point = SpatialCalculations.interpolate(start, end, 0.25)
>>> midpoint = SpatialCalculations.interpolate(start, end, 0.5)
classmethod midpoint(cls, obj1, obj2)[source]

Find the geographic midpoint between two objects.

Parameters:
Return type:

Optional[Coordinate]

Returns:

Coordinate at the midpoint, or None if coordinates unavailable

Examples

>>> coord1 = Coordinate(longitude=0, latitude=0)
>>> coord2 = Coordinate(longitude=2, latitude=2)
>>> midpoint = SpatialCalculations.midpoint(coord1, coord2)
>>> print(f"Midpoint: {midpoint.longitude}, {midpoint.latitude}")
class kmlorm.spatial.DistanceUnit(value)[source]

Bases: Enum

Units for distance measurements with conversion factors relative to kilometers.

The values represent the number of units per kilometer. For example, METERS = 1000 means 1 km = 1000 meters.

METERS = 1000.0
KILOMETERS = 1.0
MILES = 0.621371
NAUTICAL_MILES = 0.539957
FEET = 3280.84
YARDS = 1093.61
class kmlorm.spatial.HasCoordinates(*args, **kwargs)[source]

Bases: Protocol

Protocol for objects that can provide coordinates.

This protocol enables duck typing for any object that can return a Coordinate representation of itself.

__init__(*args, **kwargs)
get_coordinates()[source]

Return the coordinate representation of this object.

Return type:

Optional[Coordinate]

Returns:

Coordinate object if available, None if no coordinates exist

exception kmlorm.spatial.SpatialCalculationError[source]

Bases: Exception

Base exception for spatial calculations.

Raised when spatial calculations encounter errors that cannot be handled gracefully, such as mathematical errors or invalid geometric configurations.

exception kmlorm.spatial.InvalidCoordinateError[source]

Bases: SpatialCalculationError

Raised when coordinates are invalid or out of bounds.

This includes: - Longitude not in range [-180, 180] - Latitude not in range [-90, 90] - Non-numeric coordinate values - NaN or infinite coordinate values

Examples

>>> from kmlorm.models.point import Coordinate
>>> try:
...     coord = Coordinate(longitude=200, latitude=45)  # Invalid longitude
... except InvalidCoordinateError as e:
...     print(f"Invalid coordinate: {e}")
exception kmlorm.spatial.InsufficientDataError[source]

Bases: SpatialCalculationError

Raised when not enough data is available for spatial calculation.

This occurs when: - Objects don’t have coordinate information - Required coordinate components are missing - Input data is incomplete for the requested calculation

Examples

>>> from kmlorm.spatial.calculations import SpatialCalculations
>>> from kmlorm.models.placemark import Placemark
>>> placemark_no_coords = Placemark(name="No coordinates")
>>> coord = Coordinate(longitude=0, latitude=0)
>>> try:
...     distance = SpatialCalculations.distance_between(placemark_no_coords, coord)
... except InsufficientDataError as e:
...     print(f"Cannot calculate: {e}")

Core spatial calculations for KML ORM.

This module provides spatial calculation utilities following WGS84 datum standards. All calculations assume coordinates in decimal degrees with longitude in range [-180, 180] and latitude in range [-90, 90].

Key Features:
  • Protocol-based design for type safety

  • Multiple distance calculation strategies

  • Unit conversion support

  • Comprehensive error handling

  • Performance optimizations with caching

Examples

>>> from kmlorm.models.point import Coordinate
>>> coord1 = Coordinate(longitude=-74.006, latitude=40.7128)  # NYC
>>> coord2 = Coordinate(longitude=-0.1276, latitude=51.5074)  # London
>>>
>>> distance = SpatialCalculations.distance_between(coord1, coord2)
>>> print(f"Distance: {distance:.1f} km")
>>>
>>> bearing = SpatialCalculations.bearing_between(coord1, coord2)
>>> print(f"Bearing: {bearing:.1f} degrees")
class kmlorm.spatial.calculations.DistanceUnit(value)[source]

Bases: Enum

Units for distance measurements with conversion factors relative to kilometers.

The values represent the number of units per kilometer. For example, METERS = 1000 means 1 km = 1000 meters.

METERS = 1000.0
KILOMETERS = 1.0
MILES = 0.621371
NAUTICAL_MILES = 0.539957
FEET = 3280.84
YARDS = 1093.61
class kmlorm.spatial.calculations.HasCoordinates(*args, **kwargs)[source]

Bases: Protocol

Protocol for objects that can provide coordinates.

This protocol enables duck typing for any object that can return a Coordinate representation of itself.

get_coordinates()[source]

Return the coordinate representation of this object.

Return type:

Optional[Coordinate]

Returns:

Coordinate object if available, None if no coordinates exist

__init__(*args, **kwargs)
kmlorm.spatial.calculations.log_spatial_operation(func)[source]

Decorator to log spatial operations for monitoring and debugging.

Logs slow operations (>0.1s) and operations that return None.

Return type:

Any

class kmlorm.spatial.calculations.SpatialCalculations[source]

Bases: object

Spatial calculation utilities following WGS84 datum.

All calculations assume: - WGS84 ellipsoid (a=6378137.0m, f=1/298.257223563) - Coordinates in decimal degrees - Longitude: -180 to 180 - Latitude: -90 to 90

Mathematical Accuracy: - Haversine formula: ±0.5% for most distances - Good for distances up to ~20,000 km - Assumes spherical Earth (mean radius 6371.0088 km)

classmethod distance_between(cls, from_obj, to_obj, unit=DistanceUnit.KILOMETERS)[source]

Calculate distance between two objects with coordinates.

Parameters:
Return type:

Optional[float]

Returns:

Distance in specified units, or None if coordinates unavailable

Raises:

Examples

>>> from kmlorm.models.point import Coordinate
>>> coord1 = Coordinate(longitude=0, latitude=0)
>>> coord2 = Coordinate(longitude=1, latitude=1)
>>> distance = SpatialCalculations.distance_between(coord1, coord2)
>>> print(f"Distance: {distance:.2f} km")
>>> # Using tuples
>>> distance = SpatialCalculations.distance_between((0, 0), (1, 1))
>>> # Different units
>>> distance_miles = SpatialCalculations.distance_between(
...     coord1, coord2, unit=DistanceUnit.MILES
... )
classmethod bearing_between(cls, from_obj, to_obj)[source]

Calculate the initial bearing from one object to another.

Parameters:
Return type:

Optional[float]

Returns:

Initial bearing in degrees (0-360), or None if coordinates unavailable 0° = North, 90° = East, 180° = South, 270° = West

Raises:

SpatialCalculationError – If calculation fails

Examples

>>> coord1 = Coordinate(longitude=0, latitude=0)
>>> coord2 = Coordinate(longitude=1, latitude=0)  # Due east
>>> bearing = SpatialCalculations.bearing_between(coord1, coord2)
>>> print(f"Bearing: {bearing:.1f}°")  # Should be ~90°
classmethod midpoint(cls, obj1, obj2)[source]

Find the geographic midpoint between two objects.

Parameters:
Return type:

Optional[Coordinate]

Returns:

Coordinate at the midpoint, or None if coordinates unavailable

Examples

>>> coord1 = Coordinate(longitude=0, latitude=0)
>>> coord2 = Coordinate(longitude=2, latitude=2)
>>> midpoint = SpatialCalculations.midpoint(coord1, coord2)
>>> print(f"Midpoint: {midpoint.longitude}, {midpoint.latitude}")
classmethod distances_to_many(cls, from_obj, to_objects, unit=DistanceUnit.KILOMETERS)[source]

Calculate distances from one object to many others efficiently.

This is more efficient than calling distance_between() repeatedly because it extracts the source coordinates once and reuses the calculation.

Parameters:
Return type:

List[Optional[float]]

Returns:

List of distances in specified units (None for objects without coordinates)

Time Complexity: O(n) where n = len(to_objects) Space Complexity: O(n) for result list

Examples

>>> center = Coordinate(longitude=0, latitude=0)
>>> points = [
...     Coordinate(longitude=1, latitude=0),
...     Coordinate(longitude=0, latitude=1),
...     Coordinate(longitude=-1, latitude=0),
... ]
>>> distances = SpatialCalculations.distances_to_many(center, points)
classmethod bounding_box(cls, objects)[source]

Calculate minimum bounding rectangle for a set of objects.

Parameters:

objects (List[Union[HasCoordinates, Tuple[float, float], List[float]]]) – List of objects with coordinates

Return type:

Optional[Tuple[float, float, float, float]]

Returns:

Tuple of (min_lon, min_lat, max_lon, max_lat) or None if no valid coordinates

Examples

>>> points = [
...     Coordinate(longitude=-1, latitude=-1),
...     Coordinate(longitude=1, latitude=1),
...     Coordinate(longitude=0, latitude=2),
... ]
>>> bbox = SpatialCalculations.bounding_box(points)
>>> print(f"Bounding box: {bbox}")  # (-1, -1, 1, 2)
classmethod interpolate(cls, start, end, fraction)[source]

Find a point along the great circle path between two coordinates.

Parameters:
Return type:

Optional[Coordinate]

Returns:

Coordinate at the specified fraction along the path, or None if coordinates unavailable

Raises:

ValueError – If fraction is not in range [0, 1]

Examples

>>> start = Coordinate(longitude=0, latitude=0)
>>> end = Coordinate(longitude=10, latitude=10)
>>> quarter_point = SpatialCalculations.interpolate(start, end, 0.25)
>>> midpoint = SpatialCalculations.interpolate(start, end, 0.5)

Distance calculation strategies for spatial operations.

This module provides different strategies for calculating distances between coordinates, each with different trade-offs between accuracy and performance.

Available Strategies:
  • HaversineStrategy: Good balance of speed and accuracy (default)

  • VincentyStrategy: High accuracy for oblate spheroid (slower)

  • EuclideanApproximation: Fast approximation for small distances

Usage:
>>> from kmlorm.spatial.strategies import HaversineStrategy, VincentyStrategy
>>> strategy = HaversineStrategy()
>>> distance = strategy.calculate(40.7128, -74.006, 51.5074, -0.1276)
class kmlorm.spatial.strategies.DistanceStrategy[source]

Bases: ABC

Abstract base class for distance calculation strategies.

All strategies should implement the calculate method to compute distance between two points in decimal degrees.

abstractmethod calculate(lat1, lon1, lat2, lon2)[source]

Calculate distance between two points.

Parameters:
  • lat1 (float) – First point coordinates in decimal degrees

  • lon1 (float) – First point coordinates in decimal degrees

  • lat2 (float) – Second point coordinates in decimal degrees

  • lon2 (float) – Second point coordinates in decimal degrees

Return type:

float

Returns:

Distance in kilometers

Raises:

ValueError – If coordinates are invalid

class kmlorm.spatial.strategies.HaversineStrategy[source]

Bases: DistanceStrategy

Great circle distance using Haversine formula.

This is a good balance of speed and accuracy for most applications. Assumes a spherical Earth with mean radius of 6371.0088 km.

Accuracy: ±0.5% for most distances Performance: Fast (O(1) with simple trigonometric operations) Best for: General purpose distance calculations

Formula:

a = sin²(Δφ/2) + cos(φ1) * cos(φ2) * sin²(Δλ/2) c = 2 * atan2(√a, √(1−a)) d = R * c

Where φ is latitude, λ is longitude, R is Earth’s radius.

calculate(lat1, lon1, lat2, lon2)[source]

Calculate distance using Haversine formula.

Parameters:
  • lat1 (float) – First point coordinates in decimal degrees

  • lon1 (float) – First point coordinates in decimal degrees

  • lat2 (float) – Second point coordinates in decimal degrees

  • lon2 (float) – Second point coordinates in decimal degrees

Return type:

float

Returns:

Distance in kilometers

Time Complexity: O(1) Space Complexity: O(1)

class kmlorm.spatial.strategies.VincentyStrategy(max_iterations=100, tolerance=1e-12)[source]

Bases: DistanceStrategy

Vincenty’s formulae for accurate distance on oblate spheroid.

This strategy uses Vincenty’s inverse formula for calculating distances on an oblate spheroid (WGS84 ellipsoid). More accurate than Haversine but significantly slower.

Accuracy: ±0.5mm for distances up to ~20,000 km Performance: Slower (iterative algorithm) Best for: High-precision applications requiring maximum accuracy

Reference: T. Vincenty, “Direct and Inverse Solutions of Geodesics on the

Ellipsoid with application of nested equations”, Survey Review, vol XXIII no 176, 1975

__init__(max_iterations=100, tolerance=1e-12)[source]

Initialize Vincenty strategy.

Parameters:
  • max_iterations (int) – Maximum iterations for convergence

  • tolerance (float) – Convergence tolerance in radians

calculate(lat1, lon1, lat2, lon2)[source]

Calculate distance using Vincenty’s inverse formula.

Parameters:
  • lat1 (float) – First point coordinates in decimal degrees

  • lon1 (float) – First point coordinates in decimal degrees

  • lat2 (float) – Second point coordinates in decimal degrees

  • lon2 (float) – Second point coordinates in decimal degrees

Return type:

float

Returns:

Distance in kilometers

Raises:

ValueError – If coordinates are invalid or calculation doesn’t converge

class kmlorm.spatial.strategies.EuclideanApproximation[source]

Bases: DistanceStrategy

Fast Euclidean approximation for small distances.

Uses equirectangular projection (plate carrée) to approximate distances. Very fast but only accurate for small distances (typically <100km).

Accuracy: Good for distances <100km, decreases with distance and latitude Performance: Very fast (O(1) with minimal operations) Best for: Quick approximations, filtering, when speed is critical

Formula:

x = Δλ * cos(φm) y = Δφ d = R * √(x² + y²)

Where φm is the mean latitude.

calculate(lat1, lon1, lat2, lon2)[source]

Calculate distance using Euclidean approximation.

Parameters:
  • lat1 (float) – First point coordinates in decimal degrees

  • lon1 (float) – First point coordinates in decimal degrees

  • lat2 (float) – Second point coordinates in decimal degrees

  • lon2 (float) – Second point coordinates in decimal degrees

Return type:

float

Returns:

Distance in kilometers

Note

This is an approximation and becomes less accurate for: - Large distances (>100km) - High latitudes (near poles) - Distances crossing large longitude differences

class kmlorm.spatial.strategies.AdaptiveStrategy(high_accuracy=False)[source]

Bases: DistanceStrategy

Adaptive strategy that selects the best algorithm based on distance and requirements.

This strategy automatically chooses between different algorithms based on the characteristics of the calculation: - Euclidean for very small distances (<50km) - Haversine for medium distances (50km - 10,000km) - Vincenty for long distances (>10,000km) when high accuracy is needed

This provides a good balance of performance and accuracy for mixed workloads.

__init__(high_accuracy=False)[source]

Initialize adaptive strategy.

Parameters:

high_accuracy (bool) – Whether to prefer accuracy over speed for long distances

calculate(lat1, lon1, lat2, lon2)[source]

Calculate distance using the most appropriate strategy.

Parameters:
  • lat1 (float) – First point coordinates in decimal degrees

  • lon1 (float) – First point coordinates in decimal degrees

  • lat2 (float) – Second point coordinates in decimal degrees

  • lon2 (float) – Second point coordinates in decimal degrees

Return type:

float

Returns:

Distance in kilometers

Spatial calculation constants.

This module defines constants used in spatial calculations, including Earth parameters, coordinate system definitions, and conversion factors.

Spatial calculation exceptions.

This module defines exceptions specific to spatial calculations and operations. These exceptions provide detailed context for spatial calculation failures.

exception kmlorm.spatial.exceptions.SpatialCalculationError[source]

Bases: Exception

Base exception for spatial calculations.

Raised when spatial calculations encounter errors that cannot be handled gracefully, such as mathematical errors or invalid geometric configurations.

exception kmlorm.spatial.exceptions.InvalidCoordinateError[source]

Bases: SpatialCalculationError

Raised when coordinates are invalid or out of bounds.

This includes: - Longitude not in range [-180, 180] - Latitude not in range [-90, 90] - Non-numeric coordinate values - NaN or infinite coordinate values

Examples

>>> from kmlorm.models.point import Coordinate
>>> try:
...     coord = Coordinate(longitude=200, latitude=45)  # Invalid longitude
... except InvalidCoordinateError as e:
...     print(f"Invalid coordinate: {e}")
exception kmlorm.spatial.exceptions.InsufficientDataError[source]

Bases: SpatialCalculationError

Raised when not enough data is available for spatial calculation.

This occurs when: - Objects don’t have coordinate information - Required coordinate components are missing - Input data is incomplete for the requested calculation

Examples

>>> from kmlorm.spatial.calculations import SpatialCalculations
>>> from kmlorm.models.placemark import Placemark
>>> placemark_no_coords = Placemark(name="No coordinates")
>>> coord = Coordinate(longitude=0, latitude=0)
>>> try:
...     distance = SpatialCalculations.distance_between(placemark_no_coords, coord)
... except InsufficientDataError as e:
...     print(f"Cannot calculate: {e}")