Library / SDK
tidwall/tg avatar
tidwall/tg

tg and the deliberate narrowness of a C geometry library

Geometry library for C - Fast point-in-polygon

677 stars29 forksCMIT

At a glance

What is it?
TG is a geometry library for C, built for real-time geospatial work such as geofencing and streaming analysis, and its whole design is about point-in-polygon speed. It implements Simple Features, adds its own polygon indexing, and stops well short of being a full GIS library.
Who is it for?
TG fits a C program that has to test many points against a large polygon in real time, such as a geofencing or streaming analysis path, and that is willing to drop two files into the tree rather than take on a dependency. It is the wrong choice if you need convex hulls, Voronoi diagrams or a full GIS stack, since the project states that as a non-goal and points to GEOS.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 17 days ago.
What is it written in?
Mainly C, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A geometry library scoped to monitoring, not to GIS

TG is a geometry library for C that the project describes as small, fast and easy to use, designed for programs that need real-time geospatial work such as geofencing, monitoring and streaming analysis.

The scoping is stated just as clearly in the goals section. The main goal of TG is to provide the fastest, most memory efficient geometry library for the purpose of monitoring spatial relationships, specifically operations like point-in-polygon and geometry intersect. And there is an explicit non-goal: TG is not meant to be a full GIS library. The project tells you to consider GEOS if you need GIS algorithms such as generating a convex hull or a Voronoi diagram, and points at the separate tgx library for bridging GEOS and TG geometries so that GEOS operations can be performed on a TG geometry.

That is an unusual and useful thing for a library to state. A reader knows immediately which side of the line the project sits on, and the bridge library means the choice is not permanent: you can use TG for the hot path and reach for GEOS when you need an algorithm TG will never have.

The implementation is C11, MIT licensed, and the repository is unusually compact. The library is encapsulated in a single source file, tg.c, with a header tg.h, and the documentation notes that standard C11 is used so most modern C compilers should work. There is no build system to adopt and no package to install, which is a real advantage when you are embedding this in something with its own build.

The repository also carries a deps directory, a docs directory, an examples directory, a tests directory and a .package file, and the README links to an API reference, a polygon indexing document, a GeoBIN document, a benchmarks document and a test suite description.

Simple Features coverage, GeoJSON, WKT, WKB and GeoBIN

The format coverage is the part that decides whether TG can read the data you already have. TG implements OGC Simple Features, including Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon and GeometryCollection.

On the I/O side it reads and writes GeoJSON, WKT, WKB and GeoBIN, where GeoBIN is TG's own binary format documented separately. Three of those four are interchange formats you will already have in your system, and the fourth is the one that makes the library fast, since a compact binary form is what lets a polygon be loaded once and queried many times.

The spatial predicates are the operations the library exists to perform: intersects, covers, touches, equals and the rest of the usual set. Those names follow the OGC vocabulary, which means a developer who knows the specification can guess the function names without reading the header.

Two further capabilities are worth noting. The library compiles to WebAssembly using Emscripten, which puts a point-in-polygon engine inside a browser or a serverless function, and the test suite is described as having 100 percent coverage using sanitizers and Valgrind. For a C library, memory tooling at that level is a meaningful signal, because the failure mode of a geometry library is a leak or a use-after-free rather than a wrong answer.

The test suite lives in a tests directory with its own README, and the examples directory contains an intersects.c file, which is the worked example the documentation ends with.

Polygon indexing is the reason the library exists

TG uses entirely new indexing structures to speed up geometry predicates, and the README points at a dedicated document for the design. Two index types are named in the benchmark section, Natural and YStripes, and they are the whole difference between the two TG rows in that table.

The mechanism is straightforward. Without an index, every point-in-polygon query has to walk the polygon's edges, so the cost per point scales with the number of points in the polygon. With an index, the polygon is preprocessed into a structure that lets a query skip to the edges that could possibly contain the point. Building that structure takes time and memory once, and then each query is much cheaper.

The numbers in the README are the project's own claims, and they are specific enough to be worth reading carefully rather than as marketing. The library can index more than 10GB per second of point data on modern hardware while using less than 7 percent additional memory, and can perform over 10 million point-in-polygon operations per second even with large polygons of over 10K points. The benchmark table compares TG against GEOS on the outline of Brazil, which has 39K points, and lists operations per second, nanoseconds per operation, points, hits, build time and in-memory size for each configuration.

Two things stand out in that table. The GEOS prepared geometry row does reach millions of operations per second, so the comparison is not simply TG against a slow baseline. And the prepared GEOS row needs several times the memory of the indexed TG rows, which is the trade that matters most for a monitoring system holding many boundaries at once. The YStripes index is the fastest row and also the largest in memory, so it is a choice rather than an upgrade.

The project says the indexing structures introduce two new structures, and the polygon indexing document is where the details live.

Dropping two files into your project

There is no installer and no package manager entry. The documented approach is to drop the tg.c and tg.h files into your project, and the only build instruction given is a single compile:

sh
$ cc -c tg.c

That produces an object file you link into your program. The library uses standard C11, so most modern compilers should accept it without configuration.

The library also compiles to WebAssembly using Emscripten, which is the route to use TG in a browser or a serverless environment, and the repository's examples directory includes an intersects.c program that tests whether two geometries intersect using WKT as input.

There is one compile-time switch worth knowing about. Providing TG_NOATOMICS to the compiler disables the use of atomics and uses non-atomic reference counters instead:

sh
cc -DTG_NOATOMICS tg.c ...

That is worth knowing for two reasons. On a platform where atomics are expensive or unavailable, the switch keeps the library usable, and it also means you lose the guarantee that the reference counting is safe across threads. The atomic reference counter is what makes the implicit sharing in cloning safe, so turning it off is a decision about your threading model, not a build tweak to make things faster.

Ownership is the programmer's job, and the rules are explicit

TG library functions are thread-safe, reentrant and mostly without side effects. The exception is the use of malloc by some functions, such as the geometry constructors, and in those cases it is the programmer's responsibility to check the return value:

c
struct tg_geom *geom = tg_geom_new_point(-112, 33);
if (!geom) {
    // System is out of memory.
}

The memory rules are then stated as a one-to-one correspondence. To avoid memory leaks, you should call tg_geom_free() on geometries created from geometry constructors, geometry parsers, tg_geom_clone() and tg_geom_copy(). In other words, for every tg_geom_new_*, tg_geom_parse_*, tg_geom_clone() and tg_geom_copy() there should be eventually and exactly one tg_geom_free().

That is a simple rule, but in C it is the rule you will violate when you are tired, which is why the API is worth reading before you write the first call.

Cloning is cheap in a way that surprises people. The cloning of geometries, as with tg_geom_clone(), are O(1) operations that use implicit sharing through an atomic reference counter, and the geometry constructors use this method under the hood to maintain references to their inputs. A polygon constructor takes an exterior ring and a set of holes, and the new geometry holds cloned references to them rather than copies of the data:

c
struct tg_geom *geom = tg_geom_new_polygon(exterior, holes, nholes);

The documentation is clear that this is an implementation detail, but that the programmer needs to understand how memory and object references work. If you need a genuine deep copy instead, tg_geom_copy() provides one, and it is the one that costs you.

The type system is deliberately shallow. The object types tg_line, tg_ring and tg_poly can be safely upcast to a tg_geom at no runtime cost, which lets the same tg_geom functions work on all of them, and a tg_ring can also be cast to a tg_poly:

c
struct tg_geom *geom1 = (struct tg_geom*)line; // Cast tg_line to tg_geom
struct tg_geom *geom2 = (struct tg_geom*)ring; // Cast tg_ring to tg_geom
struct tg_geom *geom3 = (struct tg_geom*)poly; // Cast tg_poly to tg_geom

The warning is about the other direction: do not downcast, because it is not generally safe to cast from a tg_geom to other types.

Where TG is the wrong tool

The clearest wrong case is the one the project names itself. If you need convex hulls, Voronoi diagrams or the rest of the GIS algorithm set, TG will not have them and shows no sign of wanting them. The answer there is GEOS, and the tgx bridge library exists so that the two can coexist in one program.

The second wrong case is a data format mismatch. TG reads GeoJSON, WKT, WKB and its own GeoBIN, and if your data arrives in a format outside that set, there is a conversion step in front of you. The same applies to the type coverage: Simple Features as listed does not include every geometry type you might meet, and a program whose input contains something exotic needs a plan.

The third is the scale of the commitment. TG is a library, not a service, and the upside of the performance work is real but narrow: it is about point-in-polygon and intersects against large polygons. If your workload is a handful of queries against small boundaries, a general library is simpler and the difference will not show up in your latency budget.

The alternative to name here is GEOS itself, and the difference in approach is worth stating precisely. GEOS is a port of the Java Topology Suite, so it carries a broad algorithm set and a mature C++ API, and its prepared geometry concept is the closest equivalent to TG's indexing. TG is a purpose-built C library that trades that breadth for a smaller, faster, easier to embed core. The project publishes the comparison directly in its benchmark table, and the honest reading of those numbers is that both tools reach millions of operations per second on a large polygon, with GEOS prepared geometry costing more memory.

There is a third option for some workloads, which is to not use a geometry library at all. A geofence over a small number of points, or a check against a bounding box, can be written directly, and for a team that does not need the format coverage or the predicates, that is the smallest thing that works. TG earns its place when the point set is large and the polygon is complex, and not before.

Maintenance state and what a consumer inherits

The repository is not archived and the last push was on 2026-10-06, so the work is current, although there are no GitHub releases and therefore no version tags to pin against. The build and test story is in the repository rather than in a release pipeline, and the presence of a .package file at the root suggests a packaging definition for the library's own tooling.

The licence is MIT, which is the permissive end of the scale and means a commercial product can embed it without obligation beyond the notice, though that is a matter for your own review rather than advice from us.

The upgrade cost is the thing to think about. Because TG is a single source file that you copy into your tree, there is no package manager to bump for you. When a new version of TG arrives, someone on your team has to diff the new tg.c and tg.h against the ones you vendored, and decide whether to take the change. That is a real ongoing cost, and it is the price of the zero-dependency, drop-in approach. A project that would rather not track a vendored copy should look at how the library is packaged for the platform it uses.

What you get for that cost is a library with a documented API reference, a test suite at full coverage with sanitizers and Valgrind, and a benchmark page that states its own numbers in a form you can check. The combination of a small file, a clear ownership model and published measurements is why this library is worth evaluating for a hot path, and why adopting it for everything else is a mistake.

Editorial conclusion

TG fits a C program that has to test many points against a large polygon in real time, such as a geofencing or streaming analysis path, and that is willing to drop two files into the tree rather than take on a dependency. It is the wrong choice if you need convex hulls, Voronoi diagrams or a full GIS stack, since the project states that as a non-goal and points to GEOS. Before adopting it, read the ownership rules in the API documentation, since every constructed geometry needs exactly one tg_geom_free(), and decide whether the point-in-polygon speed matters more to you than the GIS breadth you would lose.

Frequently asked questions

How do I build and use the tg C geometry library?

The project is a single source file, so you drop tg.c and tg.h into your project and compile with cc -c tg.c. It uses standard C11, so most modern C compilers should work without extra configuration.

Which geometry formats can tg read and write?

TG reads and writes GeoJSON, WKT, WKB and GeoBIN, and implements OGC Simple Features including Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon and GeometryCollection.

Is tg a replacement for GEOS?

No. The project states it is a non-goal for TG to be a full GIS library, and points to GEOS for algorithms such as convex hulls or Voronoi diagrams. The tgx library bridges GEOS and TG geometries so GEOS operations can run on a TG geometry.

How do I free memory correctly when using tg?

For every tg_geom_new_*, tg_geom_parse_*, tg_geom_clone() and tg_geom_copy() there should be eventually and exactly one tg_geom_free(). Geometry constructors can fail when memory runs out, so check the returned pointer before continuing.

What does the TG_NOATOMICS define do?

Providing TG_NOATOMICS to the compiler disables the use of atomics and uses non-atomic reference counters instead, for example cc -DTG_NOATOMICS tg.c. Since the atomic reference counter is what makes the implicit sharing in cloning safe, this affects your threading model.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. tidwall/tg on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/tidwall-tg.svg)](https://hysenlabs.com/projects/tidwall-tg)

Was this useful?