"""Unit-sphere geometry and plate kinematics. Vectors are (..., 3); lat/lon in degrees."""
from __future__ import annotations
import numpy as np
def latlon_to_xyz(lat, lon):
la, lo = np.radians(lat), np.radians(lon)
return np.stack([np.cos(la) * np.cos(lo), np.cos(la) * np.sin(lo), np.sin(la)], axis=-1)
def xyz_to_latlon(p):
p = p / np.linalg.norm(p, axis=-1, keepdims=True)
return np.degrees(np.arcsin(np.clip(p[..., 2], -1, 1))), np.degrees(np.arctan2(p[..., 1], p[..., 0]))
def east_north(p):
"""Local unit east/north vectors. At the poles east is taken as +y (finite, arbitrary)."""
p = np.asarray(p, dtype=np.float64)
e = np.stack([-p[..., 1], p[..., 0], np.zeros_like(p[..., 0])], axis=-1) # z × p
ne = np.linalg.norm(e, axis=-1, keepdims=True)
polar = ne[..., 0] < 1e-12
e = np.where(polar[..., None], np.array([0.0, 1.0, 0.0]), e / np.maximum(ne, 1e-300))
n = np.cross(p, e)
return e, n
def tangent_dir(p, q):
"""Unit tangent at p pointing along the great circle toward q."""
t = q - np.sum(p * q, axis=-1, keepdims=True) * p
return t / np.maximum(np.linalg.norm(t, axis=-1, keepdims=True), 1e-15)
def gc_dist_km(p, q, radius_km):
return radius_km * np.arccos(np.clip(np.sum(p * q, axis=-1), -1.0, 1.0))
def motion_to_omega(lat, lon, azimuth_deg, speed_cm_yr, radius_km):
"""Angular velocity (rad/yr) that moves the point (lat, lon) along azimuth at speed."""
p = latlon_to_xyz(np.array([lat]), np.array([lon]))
e, n = east_north(p)
az = np.radians(azimuth_deg)
v = (np.sin(az) * e[0] + np.cos(az) * n[0]) * (speed_cm_yr / 100.0) # m/yr
return np.cross(p[0], v) / (radius_km * 1000.0)
def velocity(p, omega, radius_km):
"""Surface velocity (m/yr) of points p on plates with angular velocity omega (broadcast)."""
return np.cross(omega, p) * (radius_km * 1000.0)
def rotate_about(p, v, ang):
"""Rotate tangent vectors v about normals p by ang radians (counter-clockwise seen from outside)."""
ang = np.asarray(ang, dtype=np.float64)[..., None]
return v * np.cos(ang) + np.cross(p, v) * np.sin(ang)
def azimuth_deg(center, p):
"""Bearing (deg clockwise from north, [0, 360)) from center (3,) to points p (..., 3)."""
e, n = east_north(center[None])
t = tangent_dir(np.broadcast_to(center, p.shape), p)
return np.degrees(np.arctan2(t @ e[0], t @ n[0])) % 360.0
def great_circle_point(p0, t0, dist_km, radius_km):
"""Point reached from p0 moving dist_km along unit tangent t0."""
a = dist_km / radius_km
return np.cos(a) * p0 + np.sin(a) * t0