# 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.

C++17 Header-only Zero dependencies License: Apache-2.0 CI Coverage Latest release

vcpkg version xrepo package build2 / cppget package Conan Center (pending) Meson build

**▶ [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. ---
If this saved you an afternoon, consider starring the repo ⭐