All public symbols live in the geo:: namespace. Helpers under geo::detail::
are internal and not part of the supported API.
- Units. Coordinates are in degrees (latitude, longitude); distances in meters; angles in degrees unless otherwise noted.
- Earth model. Spherical, mean radius 6371009 m — the same model Google Maps geometry utilities use. See benchmarks.md for the trade-off vs ellipsoidal libraries like GeographicLib.
- Precision. Floating-point arithmetic; precision degrades near the poles and for antipodal pairs.
- Thread safety. All functions are pure (no global mutable state, no caching) and safe to call concurrently from multiple threads.
- Error handling. Scalar functions (
heading,offset,interpolate,distance_between,distance_to_segment) arenoexcept. Out-of-domain inputs are not asserted:interpolatewithfractionoutside[0, 1]extrapolates along the great circle,offset_originreturnsstd::nulloptwhen no origin can be computed, and pathological inputs may yield NaN. Container-taking functions (area,path_length,contains,on_edge,on_path) are not markednoexceptbecause the genericPathcontract doesn't constrainoperator[]/size()to benoexcept; they don't throw themselves. - Include strategy. Each subsystem has its own header:
<geo/latlng.hpp>(types),<geo/spherical.hpp>(distance, heading, area),<geo/poly.hpp>(point-in-polygon, on-path). The umbrella<geo/geo.hpp>pulls all three in for convenience.
Represents a geographic coordinate (latitude, longitude) in degrees.
#include <geo/latlng.hpp>geo::LatLng — a point in geographical coordinates: latitude and longitude.
- Latitude ranges between
-90and90degrees, inclusive - Longitude ranges between
-180and180degrees, inclusive. Note:180and-180are treated as equal.
geo::LatLng northPole{90, 0};
geo::LatLng otherPoint = northPole;operator== performs an approximate comparison with tolerance
LatLng::kDefaultEpsilon (= 1e-12 degrees, ≈ 0.1 nanometers on Earth).
Longitudes are compared modulo 360°, so LatLng(0, 180) == LatLng(0, -180).
For custom tolerance — e.g. comparing computation results at meter scale —
use approx_equal:
geo::LatLng a{40.7128, -74.0060};
geo::LatLng b{40.71280001, -74.00599999};
a == b; // false (off by ~1e-7°, exceeds default 1e-12)
a.approx_equal(b, 1e-5); // true (1e-5° ≈ 1 m on equator)A series of connected coordinates in an ordered sequence.
Path is a template parameter accepted by path_length, area, signed_area,
contains, on_edge, and on_path. It must be a random-access container of
geo::LatLng — specifically, it must support:
path.size()returning a size in elementspath[i]returning aLatLng(or something convertible) for0 ≤ i < size
This includes std::vector, std::array, std::span (C++20), and
std::deque. Forward-only containers like std::list, and std::initializer_list
(no operator[]), are not supported — wrap them in a std::vector first.
std::vector<geo::LatLng> aroundNorthPole = { {89, 0}, {89, 120}, {89, -120} };
std::array<geo::LatLng, 1U> northPole = { {90, 0} };Note. A braced-init-list cannot be passed directly to a
Pathtemplate parameter — it has nooperator[]and no deducible type. Wrap it in a container:// Won't compile: geo::area({{0, 0}, {0, 10}, {10, 0}}); // Wrap in a vector: geo::area(std::vector<geo::LatLng>{{0, 0}, {0, 10}, {10, 0}});
Spherical geometry utilities for computing angles, distances, and areas.
#include <geo/spherical.hpp>geo::heading(const LatLng& from, const LatLng& to) — Returns the heading from one LatLng to another. Headings are expressed in degrees clockwise from North within the range [-180, 180).
from— the starting pointto— the destination point
Returns: double — heading in degrees clockwise from north, in [-180, 180).
geo::LatLng equator{0, 0}; // on the equator at lng=0
geo::LatLng east{0, 90}; // 90° east along the equator
std::cout << geo::heading(equator, east); // +90 (due east)
std::cout << geo::heading(east, equator); // -90 (due west)geo::offset(const LatLng& from, double distance, double heading) — Returns the LatLng resulting from moving a distance from an origin in the specified heading (degrees clockwise from north).
from— the starting pointdistance— the distance to travel, in metersheading— the heading in degrees clockwise from north
Returns: LatLng — the destination point.
geo::LatLng front{0, 0};
// Quarter-circumference of Earth: π·R/2 ≈ 10,007.5 km (R = 6371009 m).
constexpr double quarter = 10'007'543.4;
auto up = geo::offset(front, quarter, 0); // { 90, 0}
auto down = geo::offset(front, quarter, 180); // { -90, 0}
auto left = geo::offset(front, quarter, -90); // { 0, -90}
auto right = geo::offset(front, quarter, 90); // { 0, 90}geo::offset_origin(const LatLng& to, double distance, double heading) — Returns the origin point that, when travelling distance meters at heading, arrives at to. Returns std::nullopt when no origin can be computed — including the degenerate cases at the poles.
to— the destination pointdistance— the distance travelled, in metersheading— the heading in degrees clockwise from north
Returns: std::optional<LatLng> — the origin, or std::nullopt if unreachable.
geo::LatLng start{40.0, -74.0};
constexpr double distance = 5'000'000.0; // 5,000 km
constexpr double heading = 60.0; // east-northeast
// Round-trip: offset_origin undoes offset.
auto destination = geo::offset(start, distance, heading);
auto origin = geo::offset_origin(destination, distance, heading);
assert(origin.has_value() && start.approx_equal(origin.value(), 1e-6));
// nullopt at the pole with quarter-circumference east heading — all
// directions are degenerate there, no origin solution exists.
auto pole = geo::offset_origin(geo::LatLng{90, 0}, 10'007'543.4, 90.0);
assert(!pole.has_value());geo::interpolate(const LatLng& from, const LatLng& to, double fraction) — Returns the LatLng which lies the given fraction of the way between the origin and the destination (spherical linear interpolation).
from— the starting pointto— the destination pointfraction— typically in[0, 1]; values outside extrapolate along the same great-circle arc
Returns: LatLng — the interpolated point on the great-circle arc from from to to.
geo::LatLng up{90, 0};
geo::LatLng front{0, 0};
// The arc from equator to pole spans 90°, so fraction 1/90 → 1° latitude.
assert(geo::LatLng{1, 0} == geo::interpolate(front, up, 1 / 90.0));
assert(geo::LatLng{89, 0} == geo::interpolate(front, up, 89 / 90.0));geo::angle_between(const LatLng& from, const LatLng& to) — Returns the central angle (great-circle arc) between two points, in radians.
Returned in radians, not degrees, because the value equals the great-circle
arc length on the unit sphere — multiply by Earth's mean radius (6371009 m)
to get meters. This is exactly how distance_between is implemented.
Returns: double — central angle in radians, in [0, π].
geo::LatLng pole{90, 0};
geo::LatLng equator{0, 0};
double angle = geo::angle_between(pole, equator); // π/2 ≈ 1.5708
double meters = angle * 6371009.0; // ≈ 1.001e+07 (quarter-circumference)geo::distance_between(const LatLng& from, const LatLng& to) — Returns the great-circle distance between two points, in meters.
Returns: double — distance in meters, in [0, π·R] (i.e. up to half Earth's circumference).
geo::LatLng up{90, 0};
geo::LatLng down{-90, 0};
std::cout << geo::distance_between(up, down); // ~20,015 km (π·R, half Earth's circumference)geo::path_length(const Path& path) — Returns the total length of the polyline (sum of great-circle distances between consecutive points), in meters.
Returns: double — total length in meters; 0 for a path with fewer than 2 points.
std::vector<geo::LatLng> path = { {0, 0}, {90, 0}, {0, 90} };
std::cout << geo::path_length(path); // ~20,015 km (π·R, half Earth's circumference)geo::area(const Path& path) — Returns the area of a closed path on Earth, in square meters. The path is implicitly closed (last vertex connects back to the first); equivalent to std::abs(signed_area(path)).
Returns: double — signed area in square meters; positive for CCW, negative for CW.
// Lune bounded by meridians 0 and 90 — one quarter of the Earth's surface.
std::vector<geo::LatLng> path = { {0, 90}, {-90, 0}, {0, 0}, {90, 0}, {0, 90} };
std::cout << geo::area(path); // ~1.275e+14 m² (π·R², one quarter of Earth's surface)geo::signed_area(const Path& path) — Returns the signed area of a closed path on Earth, in square meters.
Sign convention: counter-clockwise when viewed from outside the "inside" face of the polygon yields a positive result; clockwise yields a negative result. "Inside" is the surface that does not contain the South Pole, so for a small polygon in the northern hemisphere CCW means CCW as seen from above the North Pole.
Returns: double
// Triangle in the northern hemisphere, CCW when viewed from above the North Pole
std::vector<geo::LatLng> ccw = { {0, 0}, {0, 10}, {10, 0}, {0, 0} };
std::vector<geo::LatLng> cw = { {0, 0}, {10, 0}, {0, 10}, {0, 0} };
assert(geo::signed_area(ccw) > 0);
assert(geo::signed_area(cw) < 0);
assert(geo::signed_area(ccw) == -geo::signed_area(cw));Utilities for computations involving polygons and polylines.
#include <geo/poly.hpp>Note on
geodesicdefaults.containsdefaults to rhumb-line edges (cheaper, fine for polygons well inside one hemisphere);on_edgeandon_pathdefault to great-circle edges (more accurate, especially near the poles). Passgeodesicexplicitly when in doubt.
geo::contains(const LatLng& point, const Path& polygon, bool geodesic = false) — Returns whether the given point lies inside the specified polygon. The polygon is always considered closed. The South Pole is always outside.
geodesic—false(default) for rhumb-line edges,truefor great-circle edges. See the note at the top of this section.
Returns: bool — true if point is inside the polygon, false otherwise.
std::vector<geo::LatLng> aroundNorthPole = { {89, 0}, {89, 120}, {89, -120} };
std::cout << geo::contains(geo::LatLng{90, 0}, aroundNorthPole); // true
std::cout << geo::contains(geo::LatLng{-90, 0}, aroundNorthPole); // falsegeo::on_edge(const LatLng& point, const Path& polygon, bool geodesic = true, double tolerance = geo::kDefaultTolerance) — Returns whether the given point lies on or near a polygon edge (including the closing segment between the last and first vertices), within tolerance meters.
geodesic—true(default) for great-circle edges,falsefor rhumb-line edges. See the note at the top of this section.tolerance— maximum distance in meters betweenpointand the nearest edge to still count as "on"; defaults togeo::kDefaultTolerance(0.1 m).
Returns: bool — true if point is within tolerance of any edge.
std::vector<geo::LatLng> equator = { {0, 90}, {0, 180} };
std::cout << geo::on_edge(geo::LatLng{0, 90 - 5e-7}, equator); // true
std::cout << geo::on_edge(geo::LatLng{0, 90 - 2e-6}, equator); // falsegeo::on_path(const LatLng& point, const Path& polyline, bool geodesic = true, double tolerance = geo::kDefaultTolerance) — Returns whether the given point lies on or near a polyline, within tolerance meters. The closing segment between the first and last points is not included — that's the only difference vs on_edge.
geodesic—true(default) for great-circle edges,falsefor rhumb-line edges. See the note at the top of this section.tolerance— maximum distance in meters; defaults togeo::kDefaultTolerance(0.1 m).
Returns: bool — true if point is within tolerance of any segment of the polyline.
std::vector<geo::LatLng> polyline = { {0, 0}, {0, 10}, {10, 10}, {10, 0} };
std::cout << geo::on_path(geo::LatLng{0, 5}, polyline); // true — on first segment
std::cout << geo::on_path(geo::LatLng{5, 0}, polyline); // false — closing edge (10,0)→(0,0) is excludedgeo::distance_to_segment(const LatLng& point, const LatLng& start, const LatLng& end) — Returns the distance in meters from point to the closest point on the line segment [start, end] on the sphere. If the perpendicular foot falls outside the segment, returns the distance to the nearer endpoint.
Returns: double — distance in meters, always ≥ 0.
geo::LatLng start{28.05359, -82.41632};
geo::LatLng end{28.05310, -82.41634};
geo::LatLng point{28.05342, -82.41594};
// Point lies ~38 m east-northeast of the segment
std::cout << geo::distance_to_segment(point, start, end);| Symbol | Value | Description |
|---|---|---|
geo::kDefaultTolerance |
0.1 |
Default tolerance in meters for on_edge / on_path |