# AshGeo
### *All your Ash resources, in space!*

[![Hex Docs](](
[![Build Status](](
[![Coverage Status](](
[![GitHub Stars](](

**AshGeo** contains tools for using geospatial data in [Ash] resources and
expressions, backed by [PostGIS], [Geo], [Geo.PostGIS] and [Topo].

It provides:

- All the `st_*` functions that you would get with `Geo.PostGIS` for use with
  Ash [`expr`][Ash expressions], and [more to come](#roadmap).
- 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

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

## Configuration

### `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`:

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

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

## Usage

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

  import AshGeo.Postgis

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

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

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

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

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

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

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

- 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
- Add datalayer-independent expression predicates backed by [Topo].
- Add more informative error messages

## Developing

To get set up with the development environment, you will need a Postgres
instance with support for the PostGIS extensions listed in
`test/support/repo.ex` (the [`postgis/postgis`][postgis image] image works
nicely) and a superuser account `ash_geo_test` credentialed according to

You may now generate and apply the test migrations:

mix ash_postgres.generate_migrations

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

## 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

MIT License

Copyright (c) 2023 [bcksl]

See [] for details.

[pull request]:
[on HexDocs]:
[PostGIS reference]:
[postgis image]:
[Ash expressions]: