GeoSQL - spatial databases and GIS SQL functions

GeoSQL provides access to spatial data stored in databases such as PostGIS and Spatialite (SQLite3 extension for spatial data).

Spatial data is everywhere and mapping is a key feature in many applications. GeoSQL provides a portable and powerful toolbox for interacting with the spatial databases we store that data in.

Portable GIS functionality

GeoSQL provides access to hundreds of standard and non-standard spatial SQL functions to Ecto. This makes representing and interacting with spatial data easy in Ecto schemas and queries. Two-dimensional data as well as Z, M, and ZM variants are supported along with coordinate system support via SRIDs.

Functions are split out by standards and availability including GeoSQL.MM2 and GeoSQL.MM3. Beyond standards, GeoSQL.Common contains non-standard but widely implemented functions, and database-specific calls are also provided in e.g. the GeoSQL.PostGIS modules. Extensive support for multi-dimensional and topological queries is included. This makes it easy to see in your code which standards and backends a given piece of code requires to function.

Database-specific type extensions are provided along with utilities to e.g. decode geometries as needed.

The underlying geometry library supports WKT, WKB, and GeoJSON standards for easy data interchange. Other formats such as GML and KML are also supported via SQL functions.

defmodule MayApp.SpatialData do
  use Ecto.Schema
  use GeoSQL.MM2
  use GeoSQL.QueryUtils

  schema "spatial_data" do
    field(:name, :string)
    field(:geom, GeoSQL.Geometry)
    field(:point, GeoSQL.Geometry.Point)
    field(:pointz, GeoSQL.Geometry.PointZ)
    field(:linestring, GeoSQL.Geometry.LineString)
    field(:multipointzm, GeoSQL.Geometry.MultiPointZM)
    field(:polygon, GeoSQL.Geometry.Polygon)
    field(:multipolygon, GeoSQL.Geometry.MultiPolygon)
  end

   def features(repo) do
      from(s in __MODULE__, select: s.geom)
      |> repo.all()
   end

   def boundaries(repo) do
      from(s in __MODULE__, select: MM2.boundary(s.geom))
      |> repo.all()
      |> QueryUtils.decode_geometry(repo)
    end
end

High-level workflows

GeoSQL also provides high-level functionality, such as generating Mapbox Vector Tiles directly from a PostGIS database with a single function call. This makes it trivial to, for example, create vector map tile servers with a simple Plug:

defmodule MapTiles.VectorTilePlug do
  @behaviour Plug

  import Plug.Conn
  alias GeoSQL.PostGIS.VectorTiles

  def init(opts), do: opts

  def call(%Plug.Conn{method: "GET", params: %{"x" => x, "y" => y, "z" => z}} = conn, _opts) do
    z = Toolbelt.to_integer(z)
    x = Toolbelt.to_integer(x)
    y = Toolbelt.to_integer(y)

    layers = [
      %VectorTiles.Layer{
        name: "pois",
        source: "nodes",
        columns: %{geometry: :geom, id: :node_id, tags: :tags}
      }
    ]

    result = VectorTiles.generate(MapTiles.Repo, z, x, y, layers, "osm")

    conn
    |> put_resp_content_type("application/vnd.mapbox-vector-tile")
    |> resp(200, result)
    |> send_resp()
    |> halt()
  end
end

More workflows will be provided as the library develops.

Design Goals

  • Ease: fast to get started and hide complexity where possible.
  • Portability: schemas and queries that work across databases, hiding the many (often trivial) differences between them.
  • Completeness: extensive support for GIS SQL functions, not only the most common ones.
  • Clarity: functions organized by their availability and standards compliance.
  • Utility: out-of-the-box support for complete worfklows, to make complex tasks simple.
  • Reliability: unit tested and deployed in real-world applications.
22 Likes

Some shout-outs and gratitude …

geo_postgis

The people at Felt who make awesome mapping products have long maintained the geo and geo_postgis libraries. I’ve even contributed a patch here or there as I’ve used both of those libraries over the years in various projects.

GeoSQL began as a fork of geo_postgis when my needs grew beyond what geo_postgis provides in ways that were largely incompatible with it: spatialite, newer versions of PostGIS, high-level workflows … it’s grown far beyond that initial fork, but that is still where it started and I have immense gratitude for the work put into geo_postgis.

geometry

I moved from the geo library to geometry when I discovered that geo did not support EMPTY geometries. (I have submitted a PR to fix that, though!) While looking to see if there were other alternatives, I found the promising geometry library.

I wasn’t sure if it was still maintained as the last release was around 2 years ago, but thankfully it certainly was! The maintainer, @Marcus, was gracious enough to merge the PRs I showed up with and soon it could easily replace geo in GeoSQL.

What was really impressive was the performance, however: it uses around half the memory of geo and is anywhere from 50% to 1000%+ faster than geo depending on what one is doing with it. Consistently faster with a smaller memory footprint? Yes, please! :slight_smile:

Combined with the nice GeoJSON support and complete access to Z, ZM, and M variants of the geometry types, a big thanks to @Marcus for his work on geometry!

exqlite and ecto_sqlite3

Another key component was @warmwaffles’ SQLite3 libraries. I needed a way to extend the type support for SQLite3 databases with the Spatialite extension, and they brought in the necessary features to make that possible.

Now geometry types and spatial SQL queries work (nearly) as well with Spatialite as they do with PostGIS. I say “nearly” because SQLite is its own “fun” world of peculiar details. It’s why GeoSQL provides decode_geom and wrap_wkb functions … but it works!

The open source GIS community

The people working on things like PostGIS, Spatialite, proj, OSM, etc. get a fraction of the recognition they deserve in my opinion. They are doing amazing work in a very technical and (in terms of data) messy area of applied computer science: GIS. I’m constantly amazed by the work they do and the projects they work on and maintain for the whole world to benefit from. :slight_smile:

9 Likes

So .. where next?

Planned and/or already in progress

  • More SQL functions, for starters While there are over 290 already supported in GeoSQL, there are dozens more I plan to add support for.
  • Curves. Functions for working with curves are already there, but the geometry library does not yet support them. Types along with WTK and WKB encoding/decoding are needed, but we’re already on that and I hope to see that arrive in the coming weeks.
  • Expand the test suite. This is how I have found all sorts of strange edge cases and subtle variations in backends, and with this sort of macro-heavy code it’s pretty much a requirement. MM2 has very good coverage at this point, while MM3 and Common functions both have partial coverage. The PostGIS functions needs more work (though the vector tile functions have decent coverage). I’ll continue working to expand on these.

… and after that?

  • More workflows. The vector tile generation has been a godsend for projects I am currently working on, and I expect to add more “batteries included” modules over time.
  • GeoJSON .. maybe? geometry has very good GeoJSON support, as do the database backends, in particular PostGIS. I suspect there are opportunities here for useful things to be created.

… and things I don’t plan on working much on (but don’t let that stop you)

  • Performance. I have done essentially zero performance related work. The geometry library already gives GeoSQL a fantastic foundation to build upon there, but maybe there are other things that can be done? The vector tiles are reasonable: on the full OSM extract of Switzerland, it spits out tiles in <4ms per tile on my dev machine, which I consider sufficient for the time being. There are janky-looking things going on with the Spatialite types module, but I’ve used that same approach previously in projects where I have done performance measurements and those things are so fast as to be ignorable.
  • More backends. I’d love to see more databases supported, such as MySQL which also has an implementation of the SQL/MM standard. My own needs are covered by PostGIS and Spatialite, however, so this will depend on other motivated individuals.

PRs welcome! :slight_smile:

3 Likes

I released v1.0.0 today. The major version change was due to a breaking change in the API, driven by further experience in production use. The library should be API stable from here; near-term future work will focus on curve geometries and continuing to expand the test suite.

Changelog

  • Breaking changes
    • All SQL/MM functions were collapsed into a single MM module.
      Migration: Replace all instances of MM2 and MM3 with MM.
  • Improvements
    • Expanded unit test suite considerably, improved vector tile generation tests
  • Fixes
    • MM.locate_along was missing a parameter placeholder
3 Likes

We’re up to version 1.3 now! Not wanting to spam the board with updates to this library, I held off updating this thread until there was something bigger to announce .. and with initial Geopackage support for both Ecto Schemas and Queries there is that something.

Thanks to @simagyari for pinging me on the relevant feature request from @asianfilm which led to the exploration of Geopackage support. Turned out to be a fair amount easier than expected.

Changelog from v1.0 to v1.3

  • Geopackage support!
    • The GeoSQL.Geometry.Geopackage Ecto type allows schemas to have geometry fields that are backed by Geopackage geometry blob columns in SQLite3 databases. This does have a runtime dependency on the SpatiaLite SQLite3 module, at least for now.
    • Three functions were added to GeoSQL.SpatiaLite for use with Geopckage data: as_gpb, geom_from_gbp, and is_valid_gbp. These map to the Spatialite functions with the same names.
  • The test suite has grown considerably, with over 380 individual tests. This work will continue until all Ecto query macros have coverage.
  • Naming consistency improvements in docs, parameter naming, and internal APIs
3 Likes

I just release v1.5.1. The functionality has not changed much since 1.3 but a lot of stability work has gone into things.

The 170+ SQL/MM (GeoSQL.MM) and commonly implemented (GeoSQL.Common) GIS functions have full unit test coverage now, and along that path a number of bugs (some subtle) have been fixed. These two modules work with both SpatiaLite and PostGIS databases transparently, so that’s a significant amount of shared functionality that allows one to (mostly) just change a few lines in a configuration file and continue on.

There are some places where the two implementations calculate results slightly differently, or where SpatiaLite returns a nil result and PostGIS will return an EMPTY geometry. Thankfully those are the exceptions, and I’m considering ways to make handling those differences easier in future.

There are some new conveniences, such as the GeoSQL.Common.degrees and GeosQL.Common.radians functions which make it easy to translate from one the other in SQL queries, avoiding roundtrips and having to get the math right or as expected by the databases.

Some issues were discovered in the geometry library, as well, and patches have been pushed upstream for these.

2 Likes

Excellent library! This is another step towards expanding the geospatial/GIS ecosystem in Elixir. At my company, we’ve been working in this area and have tried to solve geospatial use cases with our own developments, often integrating libraries written in Rust/Rustler and Elixir. I appreciate the effort to continue down this path.

Thanks for the encouraging words!

If you have any feedback, requests, or thoughts on how to continue improving the Elixir GIS story, please don’t hesitate to reach out.

IMHO, Elixir can provide a very compelling GIS toolkit. There’s a reasonable amount of Elixir used in the GIS industry, but the publicly available libraries and components are a bit of a jumble and a lot of gaps remain. I’d like to help close some of those gaps, hopefully with others in the community.

I will hopefully be able to release an Elixir library for accessing data hosted on ArcGIS systems sometime in the new year, which will go a ways to addressing one of those gaps.

Other areas that need attention (at least imo) include:

  • support for OSM APIs: consuming OSM pbf, syncing local replicas with deltas, changeset APIs, etc. There is a library for Nominatim queries that works well enough, but that’s about the extent of OSM API support on hex.pm that I’m aware of.
  • support for routing software such as Valhalla (there are python and JS client libraries)
  • GeoParquet file support via Explorer
  • client-side map interactions patterns with Phoenix. A Phoenix component that wraps MabLibre and provides interaction patterns would be nice. The maplibre library is piece of that puzzle, though it probably could use more documentation, examples, etc. and currently “only” handles the style document generation.
1 Like
  • My company has been working on these cases for a while. We have a library called MaplibreX which is a wrapper of the MaplibreGl library, but with components written with LiveView and PhoenixComponents.

  • We have a vector tile server (TileXserver) that implements the Mapbox Vector Tile 2.1 specification and also other OGC Standards specifications (17 Conformance Classes)
    :white_check_mark: OGC API - Tiles Part 1: Core (4 classes)
    :white_check_mark: OGC API - Features Part 1: Core (2 classes)
    :white_check_mark: OGC API - Features Part 2: CRS (1 class)
    :white_check_mark: OGC API - Features Part 3: Filtering (2 classes)
    :white_check_mark: OGC API - Processes Part 1: Core (3 classes) :star: NEW
    :white_check_mark:TileMatrixSet (1 class)
    :hammer_and_wrench: Geoprocessing
    5 Geospatial Processes implemented with Rust NIFs:
    Buffer: Create buffers around geometries
    Simplify: Douglas-Peucker simplification
    Centroid: Calculate centroids
    Intersection: Spatial intersection testing
    Convex Hull: Convex hull envelope
    Synchronous and Asynchronous Execution
    Job Management with GenServer and ETS
    :artist_palette: Visualization
    Tile Viewer: Interactive vector tiles viewer (MapLibre GL JS)
    Processes Viewer: Web interface for geoprocessing :star: NEW
    Auto-Style Generation: Automatic MapLibre style generation
    Sprite & Font Support: Complete sprite and font support
    :bar_chart: Data Sources
    PostgreSQL/PostGIS: Direct connection to spatial databases
    MBTiles: MBTiles file support
    Composite Sources: Combine multiple sources in layers

  • A prototype routing service similar to Mapbox Navigation, implemented with Rust (NIFs), Elixir/Phoenix, and MapLibre GL JS.

    :rocket: Features
    High-Performance Backend: Rust NIFs with optimized pathfinding algorithms
    Modern Algorithms: Bidirectional A* for efficient routing
    REST API: Endpoints compatible with the Mapbox-like API
    Turn-by-Turn Navigation: Detailed navigation instructions
    Interactive Frontend: Visualization with MapLibre GL JS
    Intelligent Caching: Caching system for frequently used routes
    Multiple Profiles: Driving, Walking, Cycling
    :clipboard: Technology Stack
    Rust 1.70+: Routing algorithms and graph processing
    Elixir 1.15+: Backend API and state management
    Phoenix 1.8: Web framework
    Rustler 0.34: Elixir ↔ Rust bridge (NIFs)
    Petgraph: Graph library in Rust
    MapLibre GL JS: Map visualization
    Cachex: Distributed caching system

  • Geofencex
    A high-performance geofencing system built with Elixir, Rust, and H3 hexagonal indexing. Similar to Tile38 but leveraging Uber’s H3 spatial indexing for superior performance and scalability.

    Features
    :white_check_mark: Core Spatial Engine (Rust NIF + H3)
    H3 Hexagonal Indexing: Efficient spatial indexing using Uber’s H3 library
    Rust NIFs via Rustler: High-performance native functions for geospatial operations
    Sub-millisecond Queries: Optimized for fast spatial lookups
    :white_check_mark: REST API
    Object Management: Create, read, update, delete spatial objects
    Geofence Management: Define circular and custom geofences
    Spatial Queries:
    Nearby queries (find objects within radius)
    Within queries (find objects inside geofence)
    Distance calculations
    H3 cell information
    :white_check_mark: Interactive Frontend (MapLibre GL)
    Real-time Map Visualization: Interactive map powered by MapLibre GL
    H3 Hexagon Rendering: Visualize geofences as beautiful hexagons
    Object Tracking: Add and track spatial objects on the map
    Distance Tool: Calculate distances between points
    Cell Inspector: Click map to see H3 cell details
    Technology Stack
    Backend:

    Elixir/Phoenix - Web framework and API layer
    Rust - High-performance spatial operations
    Rustler - Elixir NIF bindings
    H3 (h3o) – Hexagonal hierarchical spatial indexing
    Frontend:

    MapLibre GL - Map rendering
    H3-js - Client-side H3 operations
    Tailwind CSS - Styling

  • Quorik
    Intelligent Asset Management Platform

    Quorik is a modern platform that combines comprehensive asset management with advanced Location Intelligence capabilities. Designed for organizations seeking to optimize their infrastructure maintenance, Quorik extracts valuable insights from data to facilitate strategic and operational decision-making. Use Rust, React, Inertia, Elixir, PostGIS

  • GeoArrow y GeoParquet support via Rustler

4 Likes

We plan to release them when they are stable.

1 Like

That’s an astounding set of features!

I’ve done a fair amount of LiveView integration with OpenLayers in the past, and am currently working more with MapLibre. Having nice support for that in a re-usable library would be great, particularly being able to forward and consume client-side map interactions on the server. I’ve written such things for specific applications, but not yet something that is suitable for reuse in other applications.

If you do end up publishing that library, I’ll 100% be on the lookout for it :slight_smile:

The tileserver sounds great, too. TBH I’m not a huge fan of many of the OGC specs, but many are widely used in industry, so … it is what it is :slight_smile: Having an Elixir server for that would be astoundingly cool.

The Geoefence and Geoparquet support also sounds incredibly nice. For routing, solutions do exist (e.g. Valhalla), even if they have their tradeoffs, but geofencing and geoparquet support from Elixir is not as easy.

I’ll definitely keep my eyes peeled for what you and your company are up to!

1 Like

Right now we’re working hard on our own CMS, which we call CatalystX, based on LiveView, and the idea is to integrate these libraries into some plugins or components for data visualization. In 2026, I’ll be more active with these products.

5 Likes

v1.6.0 was just released.

This is a significant release all the SQL functions now have test coverage, and support for the Box2D type in PostGIS is now provided.

Full changelog for 1.6.0:

  • Improvements
    • Added PostGIS.crossing_direction which translates the arcane integer return values PostGIS uses for crossing directions to a developer-friendly atom (e.g. :left)
    • Added PostGIS.dimensionality which translates the arcane integer return values PostGIS uses for dimension flags to a developer-friendly atom (e.g. :dim_3m)
    • Added support for encoding and decoding the Box2D PostGIS type via the %GeoSQL.PostGIS.Box2D{} struct
    • Parameters are now optional on PostGIS.make_valid
    • PostGIS.scale accepts Ecto fragments as parameters (not only literal numbers)
    • Test coverage of GeoSQL.PostGIS functions is complete
  • Fixes
    • Correct the number of parameters passed to PostGIS in PostGIS.swap_ordinates

Happy New Year to everyone, and may 2026 be amazing to us all!

5 Likes

Hello, I hope you are doing well, do you have plans of bringing GeoSQL to the Ash Framework

I’m not sure what would help or be useful for people working with Ash. (Probably because I haven’t used Ash myself yet!)

Do you have something specific in mind, like an Ash.Type that wraps the Geometry types?

I haven’t followed GeoSQL in detail. We’ve been using GitHub - bcksl/ash_geo: Tools for using Geo, Topo and PostGIS with Ash to access PostGIS spatial functionality in Ash - it effectively adds the Ash wrappers for geo_postgis so you should be able to follow the thinking without too much trouble.

Our usage is extremely basic at the moment - we’re just storing GPS locations for various events. For that we make use of the additional Ash types contained in the package. Over time we’ll likely look at whether the events happened where they were meant to with simple “was it in the right polygon” tests. The readme provides an example of how this kind of filter would work.

I took a quick look at ash_geo, and t would be very easy to take ash_geo and replace geo with geometry as they have similar semantics for wkb/wkt/geojson handling. That would be a search-and-replace exercise and would bring a bit more standards compliance (e.g. EMPTY in geometries) and significant performance improvements.

Where things deviate is that geo_sql is not PostGIS-specific and implements over a hundred more functions. It seems that due to a desire to use Ash.Expr, ash_geo implements all of the SQL macros itself in lib/ash_geo/postgis.ex.

I’m not familiar enough with Ash to know if there is a better way of doing that, but it seems really undesirable to duplicate things in that fashion.

If that could be figured out, geo_sql would provide access to a lot more functionality (more functions, but also SpatiaLite support).

Perhaps as a user of the ash_geo library, you could reach out to the ash_geo devs and see what they think?

I’m happy to provide support for such efforts, but as I’m not using Ash myself, I’m probably not the right person to do the adaptation.

Yeah we have the ash_geo extension but it uses the geo, geo_postgis libraries, after weeks of benchmarking I find geo_sql more appealing than the former, and I use primarily Ash framework for my backend. So I was wondering if we could have a geo_sql extension for Ash that provides a basic dsl to deal with geo spartial data, providing custom Ash types for geo_sql, letting as generate and maintain the schemas based on the data layer used with Ash, since geo_sql supports beyond postgis, this is also fine. Basically at the moment I want to use geo_sql instead of the current implementations in ash_geo and something that removes the boiler plate and implements the “describe your domain, derive the rest” design could be so much of help