diff --git a/README.md b/README.md index 15a02e6..f5822f0 100644 --- a/README.md +++ b/README.md @@ -1,72 +1,201 @@ +
+ # geo-utils-cpp -

- - CI - - - vcpkg - - - xrepo - - - Code quality - - - Coverage - - - Release - +**Google Maps geometry, ported to modern C++ — in a single header.** + +_Stop hand-rolling haversine, polyline, and polygon math yourself._ + + +

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

-

- - C++17 - - - CMake 3.14+ - - - Header-only - - - Supported platforms - - - License - + + +

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

-Practical latitude/longitude geometry for C++17 projects that need GPS math, -not a full geometry framework. + + +**▶ [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. -Distance, heading, polygon area, point-in-polygon, and path proximity checks — -header-only, no dependencies, no build step. +**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. -The API is inspired by Google Maps geometry utilities and uses the same spherical -Earth approximation model. +
+Benchmark methodology -## Features +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. -- **Lat/lng-native API** — pass latitude/longitude coordinates directly, no - framework-specific point types to convert through. -- **Header-only, dependency-free** — about 50 KB across 8 headers; nothing - to build or link. -- **Spherical math** — distance, heading, offset, interpolation, area. -- **Polygon utilities** — point-in-polygon, path proximity, snap-to-route - (`closest_point_on_path`), Douglas–Peucker simplification, and - `LatLngBounds` viewport math. -- **Polyline encoding** — `encode`/`decode` for the Google Encoded Polyline - format, including the polyline6 grid used by OSRM/Valhalla/Mapbox. -- **Fast** — matches hand-written haversine on `distance`; especially strong - on polygon `area` (see [benchmarks](docs/benchmarks.md)). -- **Focused scope** — intentionally small API for GPS, navigation, tracking, - backend, and GIS workflows. +See [docs/benchmarks.md](docs/benchmarks.md) for full methodology, all operations, and when to use +each library. + +
## Installation -### FetchContent +The fastest path is CMake **FetchContent** — no system install required: ```cmake include(FetchContent) @@ -81,6 +210,9 @@ FetchContent_MakeAvailable(GeoUtilsCpp) target_link_libraries(your_target PRIVATE geo::utils) ``` +
+Other package managers — vcpkg · xrepo · Conan · build2 · single-header · CMake wiring + ### vcpkg ```sh @@ -142,7 +274,7 @@ role: prerequisite location: https://pkg.cppget.org/1/testing ``` -### Manual +### 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 @@ -161,110 +293,45 @@ target_link_libraries(your_target PRIVATE geo::utils) For more details, see [docs/getting-started.md](docs/getting-started.md). -## Usage - -> **Try it online.** No install needed — -> [open the library in Compiler Explorer](https://godbolt.org/z/hx6W3WMsa): -> the single-header build with a runnable distance / snap-to-route / -> point-at-distance demo. - -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"; +## Requirements & compatibility - // 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}; +Any toolchain with complete C++17 support. Continuous integration builds and tests on: - 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"; -} -``` - -## Benchmarks - -`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 | — | - -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` by -1.6–1.9× on `point_at_distance`; S2 is faster on several other operations -when conversion from lat/lng is excluded. +| 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 | -See [docs/benchmarks.md](docs/benchmarks.md) for full methodology, all -operations, and when to use each library. +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. -## When not to use +## API reference -- If you need high-precision ellipsoidal geodesics or sub-meter accuracy, use - GeographicLib. -- If polygon containment is your main hot path, especially for larger polygons, - consider S2 Geometry. -- If you need many geometry types, coordinate systems, or generic geometry - algorithms, Boost.Geometry may be a better fit. -- If you need spatial indexing, use S2, CGAL, or another dedicated spatial index. +- 📖 **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) -## API Reference +## Contributing & support -See [docs/api.md](docs/api.md) for the full API reference. +Questions, bug reports, and pull requests are all welcome — +[open an issue](https://github.com/gistrec/geo-utils-cpp/issues) or send a PR. -## Support +## Credits -[Please open an issue on GitHub](https://github.com/gistrec/geo-utils-cpp/issues) +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 ⭐ +