Introduction
How far apart are these locations, really? CalculateHaversineDistance answers that directly: it computes the great-circle distance between every pair of points in a DataFrame of latitude/longitude coordinates, in kilometers, miles, or nautical miles. It's the raw distance primitive behind ConductClusterAnalysis's DBSCAN pass, exposed on its own for the more common ask — ranking which observations sit closest to which, feeding a routing or network analysis, or simply quantifying "how far apart are these things?" without running a full clustering pass.
Two points on a map look close or far apart based on their coordinates, but latitude and longitude are angular measurements on a sphere, not flat Cartesian coordinates — a one-degree change in longitude covers a very different ground distance at the equator than it does near the poles. Treating (lat, lon) pairs as if they were (x, y) points on a plane (e.g., with ordinary Euclidean distance) silently introduces distortion that grows with distance from the equator and with the distance between the points themselves. The Haversine formula instead computes the great-circle distance — the shortest path along the surface of a sphere — which is why it is the standard building block for point-to-point distance analysis in GIS, logistics routing, and pattern-of-life analysis.
Duplicate points are also worth catching before computing distances. OSINT and observational datasets frequently contain repeated readings of the same location (a sensor re-reporting, a location pinged twice), and two identical points always produce a distance of exactly zero. Left in place, they don't change any individual distance calculation, but they do inflate the number of pairs being computed and can clutter both the output table and the connecting-line visualization with redundant, uninformative zero-distance pairs. Deduplicating first keeps the pairwise result focused on genuinely distinct locations.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
dataframerequired | pd.DataFrame | — | A pandas DataFrame containing point data, with one row per point. |
longitude_column | str | 'lon' | Name of the column containing longitude values, in decimal degrees. Defaults to 'lon'. |
latitude_column | str | 'lat' | Name of the column containing latitude values, in decimal degrees. Defaults to 'lat'. |
id_column | str | None | Optional name of a column that uniquely identifies each point (e.g., a site name or ID). If provided, these labels are used to identify points in the output and on the plot. If None, the DataFrame's index is used instead. Defaults to None. |
distance_unit | str | 'km' | Unit for the returned distances. One of 'km' (kilometers), 'mi' (miles), or 'nm' (nautical miles). Defaults to 'km'. |
output_format | str | 'long' | Shape of the returned DataFrame. 'long' returns one row per unique pair of points (point_1, point_2, distance). 'matrix' returns a square DataFrame of all pairwise distances, indexed and columned by point identifier. Defaults to 'long'. |
plot_connections | bool | False | Whether to draw a matplotlib scatter plot of the points with every pairwise connection drawn as a line between them. Intended for smaller point sets, since the number of connecting lines grows with the square of the number of points. Defaults to False. |
line_color | str | 'lightgray' | Color of the connecting lines when plot_connections is True. Defaults to 'lightgray', so the lines stay faint and the points remain the visual focus. |
line_alpha | float | 0.4 | Opacity of the connecting lines when plot_connections is True, from 0 (invisible) to 1 (opaque). Defaults to 0.4. |
figure_size | tuple | (10, 8) | (width, height) of the plot in inches, when plot_connections is True. Defaults to (10, 8). |
Returns
If output_format='long': one row per unique pair of points, with columns for the two point identifiers, their coordinates, and the distance between them (named distance_km, distance_mi, or distance_nm to match distance_unit). If output_format='matrix': a square DataFrame of pairwise distances, indexed and columned by point identifier.
Example
from analysistoolbox.geospatial_analysis import CalculateHaversineDistance
import pandas as pd
# Rank how far apart a handful of field sites are from one another
sites_df = pd.DataFrame({
'site_name': ['Warehouse', 'Depot A', 'Depot B', 'Depot A'],
'lat': [34.0522, 34.1478, 33.9425, 34.1478],
'lon': [-118.2437, -118.1445, -118.4081, -118.1445]
})
distances_df = CalculateHaversineDistance(
sites_df,
id_column='site_name',
distance_unit='mi'
)
# The duplicate 'Depot A' row is dropped before distances are computed,
# and distances_df holds one row per remaining pair, e.g. Warehouse <-> Depot B.