AshGeo

all-your-ash-resources-in-space

All your Ash resources, in space!

Hex Hex Docs Downloads Build Status Coverage Status License GitHub Stars

AshGeo contains tools for using PostGIS with your Ash resources and expressions, backed by Geo and Geo.PostGIS.

It provides:

  • All the st_* functions that you would get with Geo.PostGIS for use with Ash expr, and more to come.
  • An Ash.Type backed by each of Geo.JSON, Geo.WKB and Geo.WKT which may be used as argument types in your Ash actions, and will automatically cast input from GeoJSON, WKT and WKB encodings.
  • An Ash.Type for Geo.PostGIS.Geometry, for use with resource attributes.
  • All types may be overridden and narrowed with use, allowing you to add stricter constraints and storage types (e.g. geometry(Point,26918)).
  • Validations for Geo types (such as is_point_zm(:arg) for checking that argument :arg is a instance of Geo.PointZM)
  • Validations backed by Topo, allowing checks of simple constraints such as contains? without needing to hit the database.

installation

Installation

def deps do
  [
    {:ash_geo, "~> 0.1"},
  ]
end

configuration

Configuration

config-config-exs

config/config.exs:

# Geo.PostGIS: Use Jason coder
config :geo_postgis, json_library: Jason

# Ash: Type shorthands
config :ash, :custom_types, [
  geometry: AshGeo.Geometry,
  geo_json: AshGeo.GeoJson,
  geo_wkt: AshGeo.GeoWkt,
  geo_wkb: AshGeo.GeoWkb,
  geo_any: AshGeo.GeoAny,
  # You may add shorthands for any narrowed types here
  #point26918: CoolApp.Type.GeometryPoint26918,
]

config-runtime-exs

config/runtime.exs:

# Postgrex: Geo.PostGIS types
Postgrex.Types.define(CoolApp.PostgresTypes,
  [Geo.PostGIS.Extension] ++ Ecto.Adapters.Postgres.extensions(),
  json: Jason)

# Ecto: Geo.PostGIS types
config :cool_app, CoolApp.Repo, types: CoolApp.PostgresTypes

usage

Usage

defmodule Area do
  use Ash.Resource, data_layer: AshPostgres.DataLayer

  import AshGeo.Expr

  attributes do
    uuid_primary_key :id,
    attribute :geom, :geometry, allow_nil?: false
  end

  actions do
    action :create do
      argument :geom, :geo_any

      change set_attribute(:geom, arg(:geom))
    end

    read :containing do
      argument :geom, :geo_any do
        allow_nil? false
        constraints geo_types: :point
      end

      filter expr(^st_within(^arg(:geom), geom))
    end
  end

  code_interface do
    define_for Area
    define :create, args: [:geom]
    define :containing, args: [:geom]
  end
end

Try it out:

Area.create! "POLYGON ((30 0, 20 30, 0 10, 30 0))"
Area.create! "POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))"
Area.containing! "POINT(30 30)"
Area.containing! "POINT(20 20)"
Area.containing! "POINT(10 40)"
Area.containing! "POLYGON((0 0, 30 20, 40 30, 0 0))"

The full documentation can be found on HexDocs.

roadmap

Roadmap

  • Add more PostGIS function wrappers (check out the PostGIS reference to see all that are available).
  • Continue to improve the test suite.
  • Replace validation macros with Spark DSL patches or similar.
  • Replace PostGIS fragment macros with custom predicates (ash#374)
  • Add more informative error messages (ash#365).

developing

Developing

To get set up with the development environment, you will need a Postgres instance with support for the PostGIS extensions listed in AshGeo.Test.Repo.installed_extensions() (the postgis/postgis image works nicely) and a superuser account ash_geo_test credentialed according to config/config.exs.

AshGeo uses ex_check to bundle the test configuration, and simply running mix check should closely follow the configuration used in CI.

contributing

Contributing

If you have ideas or come across any bugs, feel free to open a pull request or an issue. You can also find me on the Ash Discord as @\.

license

License

MIT License

Copyright (c) 2023 bcksl

See LICENSE.md for details.