# geo-utils-cpp
**Google Maps geometry, ported to modern C++ — in a single header.**
_Stop hand-rolling haversine, polyline, and polygon math yourself._
**[▶ Live demo](https://gistrec.github.io/geo-utils-cpp/)** — the library compiled to WebAssembly, running in your browser.
**▶ [Try it live in Compiler Explorer](https://godbolt.org/z/hx6W3WMsa)** — no install needed
~1.9× Boost (~5× S2) on area · 1.6–1.9× faster than S2 on point_at_distance → see the numbers · Apple M1 / clang 17 / -O2 -DNDEBUG
---
**Contents:** [Why](#why-geo-utils-cpp) · [API at a glance](#api-at-a-glance) · [Quick start](#quick-start) · [Which library should I pick?](#which-library-should-i-pick) · [Installation](#installation) · [Requirements & compatibility](#requirements--compatibility) · [API reference](#api-reference)
## Why geo-utils-cpp
- **Drop-in.** About 50 KB across 8 headers — no dependencies, no build step. Copy `include/`
(or the single amalgamated `geo.hpp`) and `#include `.
- **Lat/lng-native.** Pass latitude/longitude in degrees; there are no framework-specific
point types to convert through.
- **Everything for GPS work.** Distance, heading, offset, interpolation, polygon area,
point-in-polygon, path proximity, snap-to-route, Douglas–Peucker simplification, and
antimeridian-aware `LatLngBounds` viewport math.
- **Speaks the map stack.** `encode`/`decode` for the Google Encoded Polyline format, including
the polyline6 grid used by OSRM, Valhalla, and Mapbox.
- **Fast where it counts.** Matches hand-written haversine on `distance` and is especially strong
on polygon `area` — see [the benchmarks](#which-library-should-i-pick).
- **Focused scope.** A small, stable API aimed at GPS, navigation, tracking, backend, and GIS
workflows — not a full geometry framework.
The API is inspired by Google Maps geometry utilities and uses the same spherical Earth model.
## API at a glance
| Header | Key functions | What it does |
| --- | --- | --- |
| `` | `distance_between`, `heading`, `offset`, `interpolate`, `path_length`, `point_at_distance`, `area` | Great-circle distance and bearing, move-by-distance, slerp, route length, the point _N_ meters along a route, and polygon area. |
| `` | `contains`, `on_path`, `closest_point_on_path`, `simplify` | Point-in-polygon, path-proximity checks, snap-to-route projection, and Douglas–Peucker simplification. |
| `` | `encode`, `decode` | Google Encoded Polyline — precision 5, plus polyline6 for OSRM / Valhalla / Mapbox. |
| `` | `LatLngBounds`, `bounds` | Antimeridian-aware viewport rectangle: `contains`, `center`, `extend`, `intersects`. |
| `` | `LatLng`, `is_valid`, `normalized` | The degrees-in / degrees-out coordinate type used everywhere. |
| `` | — | Umbrella header that pulls in all of the above. |
Full signatures and semantics: [docs/api.md](docs/api.md).
## Quick start
Distance and heading between two points:
```cpp
#include
#include
int main() {
geo::LatLng newYork = { 40.7128, -74.0060 };
geo::LatLng london = { 51.5074, -0.1278 };
double distance = geo::distance_between(newYork, london);
double heading = geo::heading(newYork, london);
std::cout << "Distance: " << distance / 1000.0 << " km\n";
std::cout << "Heading: " << heading << " deg\n";
}
```
Polygon area, point-in-polygon, path length, and path proximity:
```cpp
#include
#include
#include
int main() {
// A small box around midtown Manhattan (vertices in CCW order).
std::vector midtown = {
{40.74, -74.01}, {40.74, -73.96}, {40.78, -73.96}, {40.78, -74.01},
};
geo::LatLng timesSquare{40.7580, -73.9855};
std::cout << "Times Square inside: "
<< (geo::contains(timesSquare, midtown) ? "true" : "false") << "\n";
std::cout << "Polygon area: "
<< geo::area(midtown) / 1e6 << " km^2\n";
// A short polyline along Broadway, and a point near it.
std::vector route = {
{40.7580, -73.9855}, // Times Square
{40.7680, -73.9818}, // Columbus Circle
{40.7780, -73.9740}, // Lincoln Center
};
geo::LatLng nearby{40.7670, -73.9820};
std::cout << "Route length: "
<< geo::path_length(route) / 1000.0 << " km\n";
std::cout << "Point within 200 m of route: "
<< (geo::on_path(nearby, route, /*geodesic=*/true, /*tolerance=*/200.0)
? "true" : "false")
<< "\n";
}
```
_Run both online, no install needed — [open them in Compiler Explorer](https://godbolt.org/z/hx6W3WMsa)._
## Which library should I pick?
`geo-utils-cpp` is header-only with no runtime dependencies. Throughput on
Apple M1 / clang 17 / `-O2 -DNDEBUG` (higher is better):
| Library | `distance_between` (M pairs/s) | `area` (poly N=100, M polys/s) | `point_at_distance` (route N=100, M queries/s) |
| -------------------- | -----------------------------: | -----------------------------: | ---------------------------------------------: |
| **geo-utils-cpp** | **40.5** | **67.2** | **0.79** |
| naive haversine | 38.3 | — | — |
| S2 Geometry | 82.9 | 14.0 | 0.50 |
| Boost.Geometry | 39.8 | 36.2 | — |
| GeographicLib | 1.2 | 2.0 | — |
### Feature matrix
| Capability | geo-utils-cpp | S2 Geometry | Boost.Geometry | GeographicLib |
| --- | :--: | :--: | :--: | :--: |
| Header-only, zero dependencies | ✅ | — | — | — |
| Lat/lng-native API (degrees in / out) | ✅ | — | — | — |
| Distance, heading, polygon area | ✅ | ✅ | ✅ | ✅ ¹ |
| Point-in-polygon & snap-to-route | ✅ | ✅ | ✅ | — |
| Google polyline encode/decode (5 & 6) | ✅ | — | — | — |
| Sub-meter WGS84 ellipsoidal geodesics | — | — | — | ✅ |
| Spatial indexing (S2 cells / R-tree) | — | ✅ | ✅ | — |
| Broad geometry types & coordinate systems | — | — | ✅ | — |
¹ GeographicLib computes on the WGS84 ellipsoid (sub-meter accuracy) rather than a sphere.
**Reach for something else when:**
- You need high-precision ellipsoidal geodesics or sub-meter accuracy — use **GeographicLib**.
- Polygon containment is your main hot path, especially for larger polygons — consider **S2 Geometry**.
- You need many geometry types, coordinate systems, or generic geometry algorithms — **Boost.Geometry**
may be a better fit.
- You need spatial indexing — use **S2**, **CGAL**, or another dedicated spatial index.
**Accuracy.** `geo-utils-cpp` uses a spherical Earth model (mean radius 6371009 m — the same model
Google Maps geometry utilities use), not the WGS84 ellipsoid. That is the right trade-off for GPS,
navigation, and tracking, but not for sub-meter surveying; precision also degrades near the poles and
for antipodal pairs. See [docs/api.md](docs/api.md) and [docs/benchmarks.md](docs/benchmarks.md) for
the details.
Benchmark methodology
Native types are pre-built outside the timed loop, so the table compares algorithmic cost rather than
object-construction overhead. `geo-utils-cpp` matches hand-written haversine and Boost.Geometry on
simple spherical operations, is especially strong on `area`, and beats `S2Polyline::Interpolate` on
`point_at_distance` by ≈1.6× at the N=100 route shown above (rising to ~1.9× for longer routes). S2 is
faster on several other operations — notably `distance_between` — when the `lat/lng → S2Point`
conversion is excluded.
See [docs/benchmarks.md](docs/benchmarks.md) for full methodology, all operations, and when to use
each library.
## Installation
The fastest path is CMake **FetchContent** — no system install required:
```cmake
include(FetchContent)
FetchContent_Declare(
GeoUtilsCpp
GIT_REPOSITORY https://github.com/gistrec/geo-utils-cpp.git
GIT_TAG v1.2.2
)
FetchContent_MakeAvailable(GeoUtilsCpp)
target_link_libraries(your_target PRIVATE geo::utils)
```
Other package managers — vcpkg · xrepo · Conan · Meson · build2 · single-header · CMake wiring
### vcpkg
```sh
vcpkg install geo-utils-cpp
```
### xrepo
```sh
xrepo install geo-utils-cpp
```
Or declare it as a dependency in your `xmake.lua`:
```lua
add_requires("geo-utils-cpp")
target("your_target")
add_packages("geo-utils-cpp")
```
### Conan
```sh
conan install --requires=geo-utils-cpp/1.2.2 --build=missing
```
Conan Center support is pending
[conan-io/conan-center-index#30152](https://github.com/conan-io/conan-center-index/pull/30152)
### Meson
The repository ships a native [`meson.build`](meson.build), so it works as a
Meson subproject with no WrapDB entry. Drop this into
`subprojects/geo-utils-cpp.wrap`:
```ini
[wrap-file]
directory = geo-utils-cpp-1.2.2
source_url = https://github.com/gistrec/geo-utils-cpp/archive/v1.2.2.tar.gz
source_filename = geo-utils-cpp-1.2.2.tar.gz
source_hash = 2ecc7726c73d7565e7820feafd944fc3d1800f586ca79e8b8b7fdb67a7ae2274
[provide]
geo-utils-cpp = geo_utils_cpp_dep
```
And consume it from your `meson.build`:
```meson
geo_utils_cpp = dependency('geo-utils-cpp')
executable('your_target', 'main.cpp', dependencies: geo_utils_cpp)
```
The library is header-only, so the subproject compiles nothing of its own: the
example smoke test is skipped automatically when built as a subproject.
### build2 / bpkg
> **Package name note:** in the build2 ecosystem this library carries the
> conventional `lib` prefix and is named **`libgeo-utils-cpp`** — i.e. the same
> `geo-utils-cpp` with `lib` in front.
> Everywhere else (GitHub, CMake, vcpkg, Conan, xrepo) it stays
> **`geo-utils-cpp`**. Same library, same headers, same `#include ` API.
Add the dependency to your package's `manifest`:
```
depends: libgeo-utils-cpp ^1.2.2
```
And in the consuming `buildfile`:
```
import libs = libgeo-utils-cpp%lib{geo-utils-cpp}
exe{hello}: cxx{hello} $libs
```
The package is published on [cppget.org](https://cppget.org/libgeo-utils-cpp)
in the `testing` section. Add that repository so the dependency above resolves —
put it in your project's `repositories.manifest`:
```
role: prerequisite
location: https://pkg.cppget.org/1/testing
```
### Manual / single-header
Copy the `include/` directory into your project and add it to your include
path. Or grab the single-header `geo.hpp` attached to
[releases](https://github.com/gistrec/geo-utils-cpp/releases) after v1.2.0
— one file, same API as `#include `.
### Using it from CMake
With any of the above methods (vcpkg, xrepo, Conan, FetchContent, or a
system `find_package`), wire it into your build with:
```cmake
find_package(GeoUtilsCpp 1.2.2 REQUIRED)
target_link_libraries(your_target PRIVATE geo::utils)
```
For more details, see [docs/getting-started.md](docs/getting-started.md).
## Requirements & compatibility
Any toolchain with complete C++17 support. Continuous integration builds and tests on:
| Platform | Compiler (CI) | Notes |
| --- | --- | --- |
| Linux | GCC, Clang | built at `-Wall -Wextra -Wpedantic -Werror` |
| macOS | AppleClang | built at `-Wall -Wextra -Wpedantic -Werror` |
| Windows | MSVC | default warning level |
The full test suite also runs under AddressSanitizer + UndefinedBehaviorSanitizer, with a libFuzzer
smoke test over `geo::decode`. The library needs only C++17; the optional CMake integration requires
CMake ≥ 3.14. Releases follow [semantic versioning](https://semver.org), and ``
exposes `GEO_UTILS_CPP_VERSION` for `#if` compatibility checks.
## API reference
- 📖 **Full API reference** — [docs/api.md](docs/api.md)
- 🚀 **New here?** — [docs/getting-started.md](docs/getting-started.md)
- ⚡ **Performance details** — [docs/benchmarks.md](docs/benchmarks.md)
## Contributing & support
Questions, bug reports, and pull requests are all welcome —
[open an issue](https://github.com/gistrec/geo-utils-cpp/issues) or send a PR.
## Credits
Ported from and API-compatible with the geometry utilities in Google Maps'
[android-maps-utils](https://github.com/googlemaps/android-maps-utils) (Apache-2.0).
## License
Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for details.
---