Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,13 @@ jobs:

- name: Test
run: ctest --test-dir build -C Release --output-on-failure

# Smoke-build the dependency-free portion of the benchmarks so that
# changes to benchmarks/CMakeLists.txt or the bench source files can't
# silently break the build. We don't run the benchmarks here — that
# requires installing S2/Boost/GeographicLib and is intentionally
# out-of-scope for CI.
- name: Smoke-build benchmarks (no external deps)
run: |
cmake -S . -B build-bench -DGEO_UTILS_CPP_BUILD_BENCHMARKS=ON -DGEO_UTILS_CPP_BUILD_TESTS=OFF -DGEO_UTILS_CPP_BUILD_EXAMPLES=OFF -DCMAKE_BUILD_TYPE=Release
cmake --build build-bench --config Release --target bench_geo_utils bench_naive
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
build/
build-*/

# Compiled Object files
*.slo
Expand Down
10 changes: 8 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ else()
endif()

include(CMakeDependentOption)
option(GEO_UTILS_CPP_BUILD_TESTS "Build geo-utils-cpp tests" ${_geo_utils_cpp_is_top_level})
option(GEO_UTILS_CPP_BUILD_EXAMPLES "Build geo-utils-cpp examples" ${_geo_utils_cpp_is_top_level})
option(GEO_UTILS_CPP_BUILD_TESTS "Build geo-utils-cpp tests" ${_geo_utils_cpp_is_top_level})
option(GEO_UTILS_CPP_BUILD_EXAMPLES "Build geo-utils-cpp examples" ${_geo_utils_cpp_is_top_level})
option(GEO_UTILS_CPP_BUILD_BENCHMARKS "Build geo-utils-cpp benchmarks" OFF)
cmake_dependent_option(GEO_UTILS_CPP_ENABLE_COVERAGE
"Enable gcov coverage instrumentation (GCC/Clang only)" OFF
"GEO_UTILS_CPP_BUILD_TESTS" OFF)
Expand Down Expand Up @@ -98,6 +99,11 @@ if(GEO_UTILS_CPP_BUILD_EXAMPLES)
add_subdirectory(examples)
endif()

# Benchmarks (opt-in: pulls Google Benchmark and optionally S2/Boost/GeographicLib)
if(GEO_UTILS_CPP_BUILD_BENCHMARKS)
add_subdirectory(benchmarks)
endif()

# Installation
include(GNUInstallDirs)

Expand Down
88 changes: 74 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,33 +35,50 @@
</a>
</p>

Header-only C++17 library for geographic (lat/lng) geometry (no dependencies).
A tiny header-only C++17 library for common latitude/longitude geometry:
distance, heading, polygon area, point-in-polygon, and path proximity checks.

Provides utilities for distance, bearing, polygon area, point-in-polygon, and
path proximity checks on Earth coordinates.
Designed for projects that need practical GPS/lat-lng math without pulling in a
large geometry framework. No dependencies, no build step, and an install
footprint of about 36 KB.

API inspired by Google Maps geometry utilities.
Uses spherical Earth approximation (like Google Maps).
The API is inspired by Google Maps geometry utilities and uses a spherical Earth
approximation, like Google Maps.

## Features

* **`geo::` spherical functions** — distance, bearing, area, interpolation
* **`geo::` polygon functions** — point-in-polygon, path proximity, distance to segments
- **Spherical calculations** — distance, heading, offset, interpolation, and area
- **Polygon utilities** — point-in-polygon and path proximity checks
- **Lat/lng-native API** — work directly with latitude/longitude coordinates
- **Simple integration** — use through CMake, vcpkg, Conan, or by copying the
`include/` directory

## Why use this library?

- Lightweight and header-only (no dependencies)
- Simple API for common GPS/lat-lng calculations
- Suitable for backend, GIS, navigation and tracking systems
- **Tiny install footprint** — about 36 KB of headers, compared with MB-scale
geometry libraries.
- **Lat/lng-native API** — pass latitude/longitude coordinates directly, without
converting to framework-specific point types.
- **Header-only and dependency-free** — easy to vendor or package; no compiled
library needs to be built or linked.
- **Fast common operations** — matches hand-written haversine on distance, stays
competitive with larger libraries, and is especially strong on polygon area.
- **Focused scope** — intentionally small API for common GPS, navigation,
tracking, backend, and GIS-related workflows.

## When not to use

- If you need high-precision geodesic calculations on an ellipsoid
- If you need advanced spatial indexing (use S2 / CGAL instead)
- 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.

## Installation

### FetchContent (recommended)
### FetchContent

```cmake
include(FetchContent)
Expand Down Expand Up @@ -89,6 +106,24 @@ find_package(GeoUtilsCpp 1.0.1 REQUIRED)
target_link_libraries(your_target PRIVATE geo::utils)
```

### Conan

> Pending Conan Center merge:
> [conan-io/conan-center-index#30152](https://github.com/conan-io/conan-center-index/pull/30152)

Once the recipe is available in Conan Center:

```sh
conan install --requires=geo-utils-cpp/1.0.1 --build=missing
```

Then in your `CMakeLists.txt`:

```cmake
find_package(GeoUtilsCpp 1.0.1 REQUIRED)
target_link_libraries(your_target PRIVATE geo::utils)
```

### find_package

```cmake
Expand All @@ -100,7 +135,7 @@ target_link_libraries(your_target PRIVATE geo::utils)

Copy the `include/` directory into your project and add it to your include path.

For more details see [docs/getting-started.md](docs/getting-started.md).
For more details, see [docs/getting-started.md](docs/getting-started.md).

## Usage

Expand All @@ -121,6 +156,31 @@ int main() {
}
```

## Benchmarks

`geo-utils-cpp` is a near-zero-overhead wrapper over the math itself, with a
tiny disk footprint thanks to header-only + zero dependencies.

| Library | Install size | `distance_between` (M pairs/s) | `area` (poly N=100, M polys/s) |
| -------------------- | ------------: | -----------------------------: | -----------------------------: |
| **geo-utils-cpp** | **36 KB** | **40.5** | **67.2** |
| naive haversine | 0 | 38.3 | — |
| S2 Geometry | 32.8 MB | 82.9 | 14.0 |
| Boost.Geometry | 12.3 MB | 39.8 | 36.2 |
| GeographicLib | 4.6 MB | 1.2 | 2.0 |

Apple M1 · clang 17 · `-O2 -DNDEBUG`. Native types pre-built outside the
timed loop — numbers reflect algorithmic cost only. Ties Boost.Geometry's
spherical strategy on `distance` / `heading` / `path_length` within noise;
**wins clearly on `area`** (allocation-free triangle-fan accumulator). S2
is faster algorithmically on `distance` / `path_length` / `contains` — but
pays a per-call `lat/lng → S2Point` conversion in real-world lat/lng
workloads (not counted here). **130–900× smaller install footprint** than
the alternatives. Zero overhead over hand-written haversine.

See [docs/benchmarks.md](docs/benchmarks.md) for the full methodology, all
operations, and a discussion of when to reach for each library.

## API Reference

See [docs/api.md](docs/api.md) for the full API reference.
Expand Down
118 changes: 118 additions & 0 deletions benchmarks/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Benchmarks for geo-utils-cpp.
#
# Build with:
# cmake -B build -DGEO_UTILS_CPP_BUILD_BENCHMARKS=ON
# cmake --build build --target bench_all
#
# Google Benchmark is fetched automatically. S2, Boost.Geometry, and
# GeographicLib are looked up via find_package — missing competitors are
# skipped with a status message instead of causing a hard error.

include(FetchContent)

# --- Google Benchmark -------------------------------------------------------

set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE)
set(BENCHMARK_ENABLE_INSTALL OFF CACHE BOOL "" FORCE)
set(BENCHMARK_INSTALL_DOCS OFF CACHE BOOL "" FORCE)
set(BENCHMARK_ENABLE_GTEST_TESTS OFF CACHE BOOL "" FORCE)

FetchContent_Declare(
google_benchmark
URL https://github.com/google/benchmark/archive/v1.8.4.tar.gz
URL_HASH SHA256=3e7059b6b11fb1bbe28e33e02519398ca94c1818874ebed18e504dc6f709be45
DOWNLOAD_EXTRACT_TIMESTAMP ON
)
FetchContent_MakeAvailable(google_benchmark)

# --- Shared infrastructure --------------------------------------------------

add_library(geo_utils_cpp_bench_common INTERFACE)
target_include_directories(geo_utils_cpp_bench_common INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/common
)
target_compile_features(geo_utils_cpp_bench_common INTERFACE cxx_std_17)

# Aggregator target: depend on every bench that ends up being built.
add_custom_target(bench_all)

function(_geo_utils_cpp_add_bench name source)
add_executable(bench_${name} ${source})
target_link_libraries(bench_${name} PRIVATE
geo::utils
geo_utils_cpp_bench_common
benchmark::benchmark_main
${ARGN}
)
add_dependencies(bench_all bench_${name})
endfunction()

# --- geo-utils-cpp itself + naive haversine baseline ------------------------

_geo_utils_cpp_add_bench(geo_utils speed/bench_geo_utils.cpp)
_geo_utils_cpp_add_bench(naive speed/bench_naive.cpp)

# --- Optional: S2 Geometry --------------------------------------------------
#
# vcpkg port name: `s2geometry`. Homebrew formula: `s2geometry`. The CMake
# package is conventionally exported as `s2`, but case-sensitive filesystems
# may also see `S2Config.cmake` from some installs — accept either.
find_package(s2 NAMES s2 S2 QUIET)
if(s2_FOUND)
_geo_utils_cpp_add_bench(s2 speed/bench_s2.cpp s2::s2)
message(STATUS "geo-utils-cpp benchmarks: S2 found — bench_s2 enabled.")
else()
message(STATUS "geo-utils-cpp benchmarks: S2 not found — bench_s2 skipped. "
"Install via 'vcpkg install s2geometry' or 'brew install s2geometry'.")
endif()

# --- Optional: Boost.Geometry -----------------------------------------------
#
# Boost.Geometry is header-only. CMake 3.30 deprecates the FindBoost module in
# favour of Boost's exported BoostConfig.cmake; we prefer CONFIG and fall back
# to the legacy module so this works across CMake 3.14+.
if(POLICY CMP0167)
cmake_policy(SET CMP0167 NEW)
endif()

find_package(Boost 1.71 QUIET CONFIG)
if(NOT Boost_FOUND)
find_package(Boost 1.71 QUIET)
endif()

if(Boost_FOUND)
# Different Boost versions expose the header set as Boost::headers (>=1.71),
# Boost::boost (older), or only via Boost_INCLUDE_DIRS.
if(TARGET Boost::headers)
_geo_utils_cpp_add_bench(boost speed/bench_boost.cpp Boost::headers)
elseif(TARGET Boost::boost)
_geo_utils_cpp_add_bench(boost speed/bench_boost.cpp Boost::boost)
else()
_geo_utils_cpp_add_bench(boost speed/bench_boost.cpp)
target_include_directories(bench_boost PRIVATE ${Boost_INCLUDE_DIRS})
endif()
message(STATUS "geo-utils-cpp benchmarks: Boost.Geometry found — bench_boost enabled.")
else()
message(STATUS "geo-utils-cpp benchmarks: Boost not found — bench_boost skipped. "
"Install via 'vcpkg install boost-geometry' or 'brew install boost'.")
endif()

# --- Optional: GeographicLib ------------------------------------------------
#
# Recent GeographicLib versions export the target `GeographicLib::GeographicLib`.
# Older versions exposed `${GeographicLib_LIBRARIES}` instead — we accept both.
find_package(GeographicLib QUIET)
if(GeographicLib_FOUND)
if(TARGET GeographicLib::GeographicLib)
_geo_utils_cpp_add_bench(geographiclib speed/bench_geographiclib.cpp
GeographicLib::GeographicLib)
else()
_geo_utils_cpp_add_bench(geographiclib speed/bench_geographiclib.cpp
${GeographicLib_LIBRARIES})
target_include_directories(bench_geographiclib PRIVATE ${GeographicLib_INCLUDE_DIRS})
endif()
message(STATUS "geo-utils-cpp benchmarks: GeographicLib found — bench_geographiclib enabled.")
else()
message(STATUS "geo-utils-cpp benchmarks: GeographicLib not found — bench_geographiclib skipped. "
"Install via 'vcpkg install geographiclib' or 'brew install geographiclib'.")
endif()
Loading
Loading