geo_postgis

repository·master·Indexed 43 days ago

https://github.com/felt/geo_postgis

A Postgrex extension for Elixir applications that enables support for PostGIS Geometry and Geography data types, leveraging the geo library for data structures. It provides integration for both Postgrex and Ecto, including macros for OpenGIS functions such as st_distance, st_dwithin, and various spatial relationship tests.

Tokens
4.4K
Snippets
16
Records
21
Agent score
53%

What's inside geo_postgis

  1. Install geo_postgis

    master

    To use geo_postgis in your Elixir project, add it to your mix.exs dependencies. Ensure that the PostGIS extension is also installed on your PostgreSQL database.

    def deps do
      [
        {:geo_postgis, "~> 3.7"}
      ]
    end
  2. Enable the PostGIS extension in PostgreSQL

    master

    Before using PostGIS features, you must ensure the extension is enabled in your database. You can do this via an Ecto migration.

    defmodule MyApp.Repo.Migrations.EnablePostgis do
      use Ecto.Migration
    
      def change do
        execute "CREATE EXTENSION IF NOT EXISTS postgis", "DROP EXTENSION IF EXISTS postgis"
      end
    end
  3. Create PostGIS columns in Ecto migrations

    master

    You can create geometry or geography columns in migrations using the :geometry type. For more advanced PostGIS configurations (like specific SRIDs or geometry types), use execute/2 to run raw SQL commands like AddGeometryColumn.

    # Simple geometry column
    defmodule Repo.Migrations.Init do
      use Ecto.Migration
    
      def change do
        create table(:test) do
          add :name,     :string
          add :geom,     :geometry
        end
      end
    end
    
    # Advanced configuration using raw SQL
    defmodule Repo.Migrations.AdvancedInit do
      use Ecto.Migration
    
      def change do
        create table(:test) do
          add :name, :string
        end
        # Add a field with specific type and SRID (4326)
        execute("SELECT AddGeometryColumn ('test','lng_lat_point',4326,'POINT',2);", "")
    
        # Create a GIST index for spatial searches
        execute("CREATE INDEX test_geom_idx ON test USING GIST (lng_lat_point);", "")
      end
    end
  4. Use geo_postgis with Ecto

    master

    When integrating with Ecto, you must define your custom types module by combining Geo.PostGIS.Extension with the standard Ecto.Adapters.Postgres.extensions(). You should also specify the json option in the definition if you want to control the JSON parser used for casting.

    # 1. Define types module combining PostGIS and Postgres extensions
    Postgrex.Types.define(MyApp.PostgresTypes,
                  [Geo.PostGIS.Extension] ++ Ecto.Adapters.Postgres.extensions(),
                  json: Jason)
    
    # 2. Configure your Repo to use these types
    config :thanks, Repo,
      database: "geo_postgrex_test",
      username: "postgres",
      password: "postgres",
      hostname: "localhost",
      adapter: Ecto.Adapters.Postgres,
      types: MyApp.PostgresTypes
    
    # 3. Use Geo.PostGIS.Geometry in your schemas
    defmodule Test do
      use Ecto.Schema
    
      schema "test" do
        field :name,           :string
        field :geom,           Geo.PostGIS.Geometry
      end
    end
  5. Configure the JSON library for GeoJSON casting

    master

    When a binary is passed to Geo.PostGIS.Geometry.cast/1 (via Ecto.Type.cast/1), it is treated as a GeoJSON string. By default, geo_postgis uses the standard JSON library to convert this binary to a map. You can specify a different JSON module (e.g., Jason) in your config.exs using the json_library key.

    # config.exs
    config :geo_postgis,
      json_library: Jason
  6. Configure the JSON parser for GeoJSON binary casting

    master

    When Geo.PostGIS.Geometry.cast/1 receives a binary, it assumes the binary is a GeoJSON string. By default, it uses Geo.JSON to decode the string into a map.

    If you need to use a different JSON parser for decoding these binaries, you can configure it via Geo.PostGIS.Config.json_library/0. This is useful if your application requires a specific JSON implementation for performance or compatibility reasons.

  7. Use Geo.PostGIS in Ecto queries

    master

    The Geo.PostGIS module provides a collection of macros that wrap PostGIS functions, allowing you to use spatial operations directly within Ecto queries. To use them, import the module into your query context.

    Currently, only OpenGIS functions are implemented.

    defmodule Example do
      import Ecto.Query
      import Geo.PostGIS
    
      def example_query(geom) do
        from location in Location, 
          limit: 5, 
          select: st_distance(location.geom, ^geom)
      end
    end
  8. Register the Geo.PostGIS.Extension with Postgrex

    master

    To use PostGIS geometry and geography types in your application, you must register Geo.PostGIS.Extension as a type module using Postgrex.Types.define/2.

    If you are using Ecto with the Postgres adapter, you should include the Ecto Postgres extensions alongside the GeoPostGIS extension to ensure all standard Postgres types are also handled correctly.

    # Standard Postgrex usage
    Postgrex.Types.define(MyApp.PostgresTypes, [Geo.PostGIS.Extension], [])
    
    # Recommended usage with Ecto
    Postgrex.Types.define(MyApp.PostgresTypes,
                        [Geo.PostGIS.Extension] ++ Ecto.Adapters.Postgres.extensions(),
                        json: Poison)
  9. Use geo_postgis with Postgrex

    master

    To use PostGIS types directly with Postgrex, define a custom types module using Postgrex.Types.define/3 and include Geo.PostGIS.Extension. Pass this module to the types option when starting your connection.

    # Define the types module
    Postgrex.Types.define(MyApp.PostgresTypes, [Geo.PostGIS.Extension], [])
    
    # Start the connection with the custom types
    opts = [hostname: "localhost", username: "postgres", database: "geo_postgrex_test", types: MyApp.PostgresTypes]
    {:ok, pid} = Postgrex.Connection.start_link(opts)
    
    # Example usage
    geo = %Geo.Point{coordinates: {30, -90}, srid: 4326}
    {:ok, _} = Postgrex.Connection.query(pid, "INSERT INTO point_test VALUES ($1, $2)", [42, geo])
  10. Use PostGIS functions in Ecto queries

    master

    You can use implemented OpenGIS functions in Ecto queries by importing Geo.PostGIS. This allows you to perform spatial operations like st_distance directly within your from macros.

    defmodule Example do
      import Ecto.Query
      import Geo.PostGIS
    
      def example_query(geom) do
        query = from location in Location, 
                limit: 5, 
                select: st_distance(location.geom, ^geom)
        query
        |> Repo.one()
      end
    end
  11. Configure the JSON library for GeoPostGIS

    master

    By default, geo_postgis uses the JSON module if it is loaded; otherwise, it falls back to Poison. This is used when a binary is passed to Geo.PostGIS.Geometry.cast/1 (via Ecto.Type.cast/1), where the binary is assumed to be a GeoJSON string. You can override the default JSON parser by setting the :json_library key in your application configuration under the :geo_postgis application name.

    # In your config/config.exs or similar
    config :geo_postgis, json_library: MyCustomJsonModule
  12. Configure and use Geo.PostGIS.Extension in a Postgrex connection

    master

    Once your custom types module is defined, pass it to the :types option when starting a Postgrex.Connection. This allows you to pass Geo structs (like %Geo.Point{}) directly into queries, and the extension will handle the encoding to WKB (Well-Known Binary) and decoding from the database.

    # 1. Define types
    Postgrex.Types.define(MyApp.PostgresTypes, [Geo.PostGIS.Extension], [])
    
    # 2. Start connection with the custom types module
    opts = [hostname: "localhost", username: "postgres", database: "geo_test", types: MyApp.PostgresTypes]
    {:ok, pid} = Postgrex.Connection.start_link(opts)
    
    # 3. Use Geo structs in queries
    geo = %Geo.Point{coordinates: {30, -90}, srid: 4326}
    {:ok, _} = Postgrex.Connection.query(pid, "INSERT INTO point_test VALUES ($1, $2)", [42, geo])
    
    # 4. Retrieve Geo structs from results
    {:ok, %Postgrex.Result{rows: [{id, geom}]}} = Postgrex.Connection.query(pid, "SELECT id, geom FROM point_test")
    # geom will be a %Geo.Point{} struct