Type System
The pygeohash
library provides a comprehensive type system to help users write type-safe code and get better IDE support. This includes both basic types for working with geohashes and specialized types for NumPy and Pandas integration.
Core Types
These are the fundamental types used throughout the library:
LatLong
A named tuple representing a latitude/longitude coordinate pair:
from pygeohash import LatLong location: LatLong = LatLong(latitude=37.7749, longitude=-122.4194) print(f"Latitude: {location.latitude}, Longitude: {location.longitude}")
ExactLatLong
A named tuple that includes error margins for latitude and longitude:
from pygeohash import ExactLatLong, decode_exactly exact_location: ExactLatLong = decode_exactly("9q9hwg") print(f"Lat: {exact_location.latitude} ± {exact_location.latitude_error}") print(f"Lon: {exact_location.longitude} ± {exact_location.longitude_error}")
BoundingBox
A named tuple representing a geographical bounding box:
from pygeohash import BoundingBox, get_bounding_box bbox: BoundingBox = get_bounding_box("9q9hwg") print(f"Min Lat: {bbox.min_lat}, Max Lat: {bbox.max_lat}") print(f"Min Lon: {bbox.min_lon}, Max Lon: {bbox.max_lon}")
Validation Types
The library provides special types and validation functions for geohashes and coordinates. These help catch errors early and make code more maintainable.
Validation Functions
The library provides three pairs of validation functions:
Check functions that return boolean: -
is_valid_geohash(value: str) -> bool
-is_valid_latitude(value: float) -> bool
-is_valid_longitude(value: float) -> bool
Assertion functions that return validated types: -
assert_valid_geohash(value: str) -> Geohash
-assert_valid_latitude(value: float) -> Latitude
-assert_valid_longitude(value: float) -> Longitude
Basic Usage
Here’s how to use the validation functions:
from pygeohash import (
Geohash, Latitude, Longitude,
assert_valid_geohash, assert_valid_latitude, assert_valid_longitude,
is_valid_geohash, is_valid_latitude, is_valid_longitude,
)
# Using check functions (returns bool)
if is_valid_geohash("9q9hwg"):
print("Valid geohash!")
if is_valid_latitude(37.7749) and is_valid_longitude(-122.4194):
print("Valid coordinates!")
# Using assertion functions (returns validated type)
try:
geohash: Geohash = assert_valid_geohash("9q9hwg")
lat: Latitude = assert_valid_latitude(37.7749)
lon: Longitude = assert_valid_longitude(-122.4194)
except ValueError as e:
print(f"Validation failed: {e}")
Validation Rules
The validation functions enforce the following rules:
Geohash validation: - Must contain only base32 characters (0-9, b-h, j-k, m-n, p-z) - Must be between 1 and 12 characters long - Cannot be empty or None
Latitude validation: - Must be between -90 and 90 degrees inclusive - Must be a valid float number
Longitude validation: - Must be between -180 and 180 degrees inclusive - Must be a valid float number
Best Practices
Here are some recommended patterns for using the validation types:
Validate early:
from pygeohash import assert_valid_geohash, assert_valid_latitude, assert_valid_longitude
def process_location(geohash: str, lat: float, lon: float) -> None:
# Validate all inputs immediately at function start
validated_geohash = assert_valid_geohash(geohash)
validated_lat = assert_valid_latitude(lat)
validated_lon = assert_valid_longitude(lon)
# Rest of the function can assume valid data
...
Use type hints with validated types:
from pygeohash import Geohash, Latitude, Longitude
def calculate_distance(
geohash1: Geohash,
lat: Latitude,
lon: Longitude,
) -> float:
# Function can assume inputs are already validated
...
Handle validation errors appropriately:
from pygeohash import assert_valid_geohash, is_valid_latitude, is_valid_longitude
def safe_process_location(geohash: str, lat: float, lon: float) -> None:
# Check without raising for coordinates
if not is_valid_latitude(lat) or not is_valid_longitude(lon):
print(f"Warning: Invalid coordinates ({lat}, {lon})")
return
try:
# Assert for geohash (will raise if invalid)
validated_geohash = assert_valid_geohash(geohash)
except ValueError as e:
print(f"Error: {e}")
return
# Process validated data
...
Use with NumPy and Pandas:
import numpy as np
import pandas as pd
from pygeohash import (
assert_valid_geohash,
assert_valid_latitude,
assert_valid_longitude,
)
# Validate NumPy arrays
def validate_coordinate_arrays(
lats: np.ndarray,
lons: np.ndarray,
) -> tuple[np.ndarray, np.ndarray]:
# Vectorized validation
valid_lats = np.logical_and(lats >= -90, lats <= 90)
valid_lons = np.logical_and(lons >= -180, lons <= 180)
if not np.all(valid_lats):
raise ValueError("Invalid latitudes found")
if not np.all(valid_lons):
raise ValueError("Invalid longitudes found")
return lats, lons
# Validate Pandas Series
def validate_geohash_series(s: pd.Series) -> pd.Series:
# Apply validation to each element
return s.apply(assert_valid_geohash)
Common Validation Errors
Here are the common validation errors you might encounter:
Invalid geohash format:
# These will raise ValueError
assert_valid_geohash("") # Empty string
assert_valid_geohash("!") # Invalid characters
assert_valid_geohash("9q9hwg" * 3) # Too long (>12 chars)
Invalid coordinate values:
# These will raise ValueError
assert_valid_latitude(91) # Above 90 degrees
assert_valid_latitude(-91) # Below -90 degrees
assert_valid_longitude(181) # Above 180 degrees
assert_valid_longitude(-181) # Below -180 degrees
Collection Types
The library provides type aliases for collections of geohashes:
GeohashCollection
A generic iterable of geohash strings:
from pygeohash import GeohashCollection, mean def calculate_center(geohashes: GeohashCollection) -> str: return mean(geohashes)
GeohashList
A concrete list of geohash strings:
from pygeohash import GeohashList geohashes: GeohashList = ["9q9hwg", "9q9hwy", "9q9hwv"]
NumPy Integration
For users working with NumPy arrays, the library provides specialized array types:
GeohashArray
A NumPy array of geohash strings:
import numpy as np from pygeohash import GeohashArray, encode # Create a grid of coordinates lats = np.array([37.7749, 37.7750, 37.7751]) lons = np.array([-122.4194, -122.4195, -122.4196]) # Convert to geohashes geohashes: GeohashArray = np.array([ encode(lat, lon) for lat, lon in zip(lats, lons) ])
LatitudeArray
andLongitudeArray
NumPy arrays for latitude and longitude values:
from pygeohash import LatitudeArray, LongitudeArray latitudes: LatitudeArray = np.array([37.7749, 37.7750, 37.7751]) longitudes: LongitudeArray = np.array([-122.4194, -122.4195, -122.4196])
Pandas Integration
For users working with Pandas, the library provides specialized Series and DataFrame types:
GeohashSeries
,LatitudeSeries
, andLongitudeSeries
Pandas Series for geohash strings and coordinates:
import pandas as pd from pygeohash import GeohashSeries, LatitudeSeries, LongitudeSeries geohashes: GeohashSeries = pd.Series(["9q9hwg", "9q9hwy", "9q9hwv"]) latitudes: LatitudeSeries = pd.Series([37.7749, 37.7750, 37.7751]) longitudes: LongitudeSeries = pd.Series([-122.4194, -122.4195, -122.4196])
GeohashDataFrame
A typed DataFrame with geohash-related columns:
from pygeohash import GeohashDataFrame # Create a DataFrame with typed columns df = GeohashDataFrame({ 'geohash': ["9q9hwg", "9q9hwy", "9q9hwv"], 'latitude': [37.7749, 37.7750, 37.7751], 'longitude': [-122.4194, -122.4195, -122.4196] }) # Type checking will ensure these columns exist and have correct types print(df.geohash) # GeohashSeries print(df.latitude) # LatitudeSeries print(df.longitude) # LongitudeSeries
Utility Types
The library also includes utility types for specific purposes:
Direction
A literal type for cardinal directions:
from pygeohash import Direction, get_adjacent def get_neighbor(geohash: str, dir: Direction) -> str: return get_adjacent(geohash, dir) # dir must be "right", "left", "top", or "bottom"
GeohashPrecision
A type representing valid geohash precision values:
from pygeohash import GeohashPrecision, encode def create_geohash(lat: float, lon: float, prec: GeohashPrecision = 6) -> str: return encode(lat, lon, prec) # prec must be between 1 and 12
Type Safety and Fallbacks
The library’s type system is designed to be both helpful and unobtrusive:
During development and type checking: - Full type information is available for IDE support - Type checkers will catch type-related errors
At runtime: - If NumPy/Pandas are not available, types fall back to standard Python types - No runtime overhead or dependencies are added
Example: Type-Safe Function
Here’s an example of how to write a type-safe function that works with different input types:
from typing import Union
from pygeohash import (
GeohashCollection, GeohashArray, GeohashSeries,
LatitudeArray, LongitudeArray,
encode
)
def process_coordinates(
lats: Union[LatitudeArray, list[float]],
lons: Union[LongitudeArray, list[float]],
precision: GeohashPrecision = 6
) -> GeohashCollection:
"""Process coordinates and return geohashes.
Works with both NumPy arrays and Python lists.
"""
return [encode(lat, lon, precision) for lat, lon in zip(lats, lons)]
This type system helps catch errors early, provides better IDE support, and makes the code more maintainable while remaining flexible for different use cases.