Open-source project
felt/geo avatar
felt/geo

felt/geo: geometry parsing and encoding for Elixir

A collection of GIS functions for Elixir

678 stars100 forksElixirMIT

At a glance

What is it?
Geo is a Hex package that converts between WKT, WKB and GeoJSON representations of geometry in Elixir, covering seventeen geometry types. It is a format layer, not a spatial database or a computational geometry engine, and the README points to two other projects for those jobs.
Who is it for?
Geo fits an Elixir service that receives or emits geometry in WKT, WKB or GeoJSON and wants a small, well defined conversion layer with a struct representation and srid carried along. It does not fit a team that needs spatial queries, indexing, projections beyond srid tagging, or in-memory geometry maths, and the README is explicit that the PostGIS extension and the in-memory calculation library are separate projects.
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 46 days ago.
What is it written in?
Mainly Elixir, 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 format layer, not a spatial engine

Geo is a collection of GIS functions for Elixir, and the scope is narrower than that phrase first suggests. It handles conversions to and from well-known text, well-known binary and GeoJSON. That is the whole job.

The distinction matters because Elixir already has a few ways to get geometry into a program and the boundary here is deliberate. Geo does not talk to a database, it does not run spatial queries, and it does not compute distances or intersections. The README points at two sibling projects for those needs, geo_postgis for the Postgrex PostGIS extension and topo for geospatial calculations performed in memory against Geo's own structs.

That framing tells you the audience. It is for the Elixir developer who already has a PostGIS database or receives geometry over an HTTP API, and who needs a dependable, testable way to move between the wire formats and a set of plain structs. The structs are ordinary Elixir terms with a coordinates tuple and an srid field, so they pattern match and serialize without ceremony.

The package is on Hex as geo, with a module version badge and Hex documentation links, and the repository carries a build and test workflow badge for the master branch. Copyright is held by Bryan Joseph, and the code is released under the MIT licence with the licence text in the repository.

Seventeen geometry types, including the Z and M variants

The type list in the README is long enough to be worth reading in full, because the presence or absence of a variant usually decides whether a library fits your data. Geo handles Point, PointZ, PointM and PointZM, LineString, LineStringZ and LineStringZM, Polygon, PolygonZ, MultiPoint, MultiPointZ, MultiLineString, MultiLineStringZ, MultiLineStringZM, MultiPolygon, MultiPolygonZ, and GeometryCollection.

Two observations. First, the Z and M variants are modelled for most families but not all of them: there is no PolygonZM, no MultiPointZM, no MultiPolygonZM, and MultiPointM is missing too. If your data carries measure values alongside elevation on a multipoint, this library will not hold it. Second, the coverage is not symmetric between the families, which means the project treats a full cross product as unnecessary work rather than an oversight.

The distinction between a geometry with a measure and one with a coordinate dimension is not a detail you can skip, since WKT, WKB and GeoJSON each encode these differently. A library that quietly drops an M value on decode is a source of silent data loss, so this is worth testing against your own data rather than assuming.

GeometryCollection sits in the list as a single entry, which is what you would expect from a parser rather than a computational library that would need to reason about heterogeneous members.

WKT and EWKT decoding carries the srid through the struct

The README's WKT examples show the shape of the API and, more usefully, show how the srid travels. Decoding a bare WKT point gives a struct with a nil srid, while decoding the extended form with an SRID prefix gives the same coordinates with the srid attached:

elixir
iex(1)> {:ok, point} = Geo.WKT.decode("POINT(30 -90)")
%Geo.Point{ coordinates: {30, -90}, srid: nil}

iex(2)> Geo.WKT.encode!(point)
"POINT(30 -90)"

iex(3)> point = Geo.WKT.decode!("SRID=4326;POINT(30 -90)")
%Geo.Point{coordinates: {30, -90}, srid: 4326}

Two API conventions are visible here. The non-bang functions return a tagged tuple, so a parse failure is a value you have to match on rather than an exception. The bang functions raise instead, and the bang decode is what handles the EWKT form with the SRID prefix. Geo.WKT.encode! takes a struct and returns the string, while the decoding side returns {:ok, struct} or {:error, reason} in the safe form.

The srid is stored rather than applied. Geo records which spatial reference a coordinate belongs to, and it is the calling code that must decide what to do with that number. There is no reprojection in this library, no axis handling and no unit conversion. For a service that receives coordinates in a known system and stores them in another, that work is yours.

WKB and EWKB: binary hex in, binary hex out

The binary path follows the same conventions with a different payload. The WKB examples decode a hex string into a struct and encode a struct back into hex, and the extended form again carries the srid:

elixir
iex(1)> {:ok, point} = Geo.WKB.decode("0101000000000000000000F03F000000000000F03F")
%Geo.Point{ coordinates: {1.0, 1.0}, srid: nil }

iex(2)> Geo.WKB.encode!(point)
"00000000013FF00000000000003FF0000000000000"

iex(3)> point = Geo.WKB.decode!("0101000020E61000009EFB613A637B4240CF2C0950D3735EC0")
%Geo.Point{ coordinates: {36.9639657, -121.8097725}, srid: 4326 }

Notice that the binary decoder takes and returns hex strings rather than raw bytes, which is a deliberate convenience for the Elixir caller who is likely handling a hex payload from a database driver. The coordinates come back as floats, as you would expect from a binary format that stores doubles.

The encoding examples are also a good reminder that binary geometry is byte-order sensitive. The EWKB example that comes back with the coordinates reversed in sign is a case where the same library produces different output depending on how the struct was built, and the ordering of the coordinate pair in the struct is what decides it. If your upstream system sends little-endian WKB and your database expects big-endian, the srid tag is not the only thing to check.

Nothing in the README describes support for a PostGIS hex format directly. For that, the README points to the geo_postgis project.

GeoJSON stops at the map, so you choose the JSON library

The GeoJSON path is the one place where the README is explicit about a boundary. It states that Geo only encodes and decodes maps shaped as GeoJSON, and that JSON encoding and decoding must be done before and after. The example uses JSON as the parser and pipes into Geo.JSON.decode:

elixir
# Examples using JSON as the JSON parser

iex(1)> Geo.JSON.encode(point)
{:ok, %{ "type" => "Point", "coordinates" => [100.0, 0.0] }}

iex(2)> point = JSON.decode!("{\"type\": \"Point\", \"coordinates\": [100.0, 0.0] }") |> Geo.JSON.decode
{:ok, %Geo.Point{coordinates: {100.0, 0.0}, srid: 4326, properties: %{}}

The design is sensible. GeoJSON is a JSON document format, and the geometry layer has no business owning a JSON parser. The consequence is that you pick the parser, and in a project that already uses Jason or another library, Geo does not add a dependency for that job.

Two things in that output deserve a second look. The decoded struct has an srid of 4326 even though the GeoJSON in the example carries no crs member, so the default assumption is applied silently. And the properties map is present as an empty map, which means feature properties are carried on the struct rather than discarded.

The struct is a plain Elixir map with string keys on the GeoJSON side, and a plain map with atom keys in the struct. If you compare two geometries with ==, a properties key of %{} against a missing key will differ, which matters if you are writing equality checks in tests.

Adding Geo to a Mix project

Installation is a Hex dependency, and the README gives the dependency entry directly:

elixir
defp deps do
  [
    {:geo, "~> 4.0"}
  ]
end

The version constraint is ~> 4.0, which allows patch-level updates within 4.x but not a jump to 5. That matches the published release history: v4.1.0 on 2025-09-12, v4.0.1 on 2024-09-24 and v4.0.0 on 2024-09-17.

A first use looks like the README examples. Decode a geometry with Geo.WKT.decode, pattern match on {:ok, geometry}, and pass the struct to whatever you need to write out. If you already have a string you know is valid, the bang variants skip the tuple. For GeoJSON, wrap the decode with your own JSON library first.

The repository layout is small and conventional: lib for the modules, test for the suite, bench for benchmarks, plus mix.exs, mix.lock, a formatter configuration and a CHANGELOG.md. The presence of a bench directory is worth a note, since it indicates the project considers encode and decode performance worth measuring, and the CI badge in the README points at an elixir-build-and-test workflow on the master branch.

Where Geo ends, and what the last push tells you

The limits are mostly about scope rather than defects. Geo does not do spatial predicates, does not buffer or intersect geometries, does not project between coordinate systems and does not index. The README anticipates this twice, pointing to geo_postgis when you want the PostGIS extension for Postgrex, and to topo when you want geospatial calculations in memory on Geo's structs. That separation is a feature if you want a small dependency, and a limitation if you end up pulling in two more packages to finish the job.

There is also the type coverage gap described earlier, where several ZM and M combinations are missing from the list.

On maintenance, the last push to the repository was on 2026-09-07, and the repository is not archived. The most recent release tag, v4.1.0, was published on 2025-09-12, which is a gap of roughly a year between the latest tag and the last push. The default branch is master rather than main, and the repository still carries a .travis.yml file alongside the GitHub Actions workflow, so the CI configuration spans two generations of tooling.

For an Elixir library this is a stable-looking dependency, but the year between the last release and the last commit is worth a look at the CHANGELOG.md before you pin a version, and worth checking whether the version range you depend on is one the maintainer still intends to support.

Editorial conclusion

Geo fits an Elixir service that receives or emits geometry in WKT, WKB or GeoJSON and wants a small, well defined conversion layer with a struct representation and srid carried along. It does not fit a team that needs spatial queries, indexing, projections beyond srid tagging, or in-memory geometry maths, and the README is explicit that the PostGIS extension and the in-memory calculation library are separate projects. Before adopting it, check the geometry type you need against the seventeen listed in the README, decide how you will handle the GeoJSON JSON parsing step it leaves to you, and pin to the 4.x line the package metadata advertises.

Frequently asked questions

What does the geo package for Elixir do?

It converts geometries to and from well-known text, well-known binary and GeoJSON, covering seventeen geometry types including the Z and M variants of points and lines.

How do I add geo to an Elixir project?

The README shows the dependency as {:geo, "~> 4.0"} in the deps list, and the package is published on Hex as geo with API documentation on HexDocs.

Does geo handle GeoJSON parsing in Elixir?

No. The README states that Geo only encodes and decodes maps shaped as GeoJSON, so JSON encoding and decoding must be done before and after using a parser of your choice.

Can geo do spatial queries or calculations in Elixir?

No. The README points to geo_postgis for the Postgrex PostGIS extension and to topo for geospatial calculations in memory with Geo's structs.

Official sources

  1. felt/geo on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
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/felt-geo.svg)](https://hysenlabs.com/projects/felt-geo)

Was this useful?