README.md

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

[![Hex](http://img.shields.io/hexpm/v/ash_geo.svg?style=flat)](https://hex.pm/packages/ash_geo)
[![Hex Docs](https://img.shields.io/badge/hex-docs-purple.svg)](https://hexdocs.pm/ash_geo/)
[![Downloads](https://img.shields.io/hexpm/dt/ash_geo.svg)](https://hex.pm/packages/ash_geo)
[![Build Status](https://img.shields.io/github/actions/workflow/status/bcksl/ash_geo/ci.yml)](https://github.com/bcksl/ash_geo)
[![Coverage Status](https://coveralls.io/repos/github/bcksl/ash_geo/badge.svg?branch=main)](https://coveralls.io/github/bcksl/ash_geo?branch=main)
[![License](https://img.shields.io/github/license/bcksl/ash_geo?color=blue)](https://github.com/bcksl/git_opts/blob/main/LICENSE.md)
[![GitHub Stars](https://img.shields.io/github/stars/bcksl/ash_geo?color=ffd700&label=github&logo=github)](https://github.com/bcksl/ash_geo)

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

```elixir
def deps do
  [
    {:ash_geo, "~> 0.3.0"},
  ]
end
```

This package provides a collection of non-overlapping functionality based on
several dependencies, not all of which may be necessary your application.
Therefore, the dependencies for the functionality you wish to use must be added
alongside `:ash_geo`.

- For `Topo` validations, `:topo` must be added.
- For Postgis expressions, `:geo_postgis` must be added.

## Configuration

### `config/config.exs`:

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

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

```elixir
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
  end

  actions do
    create :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:

```elixir
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
  ([`ash#374`](https://github.com/ash-project/ash/issues/374))
- Add datalayer-independent expression predicates backed by Topo.
- Add more informative error messages
  ([`ash#365`](https://github.com/ash-project/ash/issues/365)).

## 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
`config/config.exs`.

You may now generate and apply the test migrations:

```sh
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](https://discord.gg/D7FNG2q) as `@\`.

## License

MIT License

Copyright (c) 2023 [bcksl]

See [LICENSE.md] for details.

[bcksl]: https://github.com/bcksl
[LICENSE.md]: https://github.com/bcksl/ash_geo/blob/main/LICENSE.md
[pull request]: https://github.com/bcksl/ash_geo/pulls
[issue]: https://github.com/bcksl/ash_geo/issues
[on HexDocs]: https://hexdocs.pm/ash_geo
[PostGIS]: https://postgis.net/
[PostGIS reference]: https://postgis.net/docs/reference.html
[postgis image]: https://hub.docker.com/r/postgis/postgis
[Geo]: https://github.com/bryanjos/geo
[Geo.PostGIS]: https://github.com/bryanjos/geo_postgis
[Ash]: https://github.com/ash-project/ash
[Ash expressions]: https://hexdocs.pm/ash/expressions.html