lib/geo/wkb.ex

defmodule Geo.WKB do
  @moduledoc """
  Converts to and from WKB and EWKB.

  It supports WKB both as base-16 encoded strings or as binaries.

  ## Examples

      iex> {:ok, point} = Geo.WKB.decode("0101000000000000000000F03F000000000000F03F")
      Geo.Point[coordinates: {1, 1}, srid: nil]

      iex> Geo.WKT.encode!(point)
      "POINT(1 1)"

      iex> point = Geo.WKB.decode!("0101000020E61000009EFB613A637B4240CF2C0950D3735EC0")
      Geo.Point[coordinates: {36.9639657, -121.8097725}, srid: 4326]

  """

  alias Geo.WKB.{Encoder, Decoder}

  @doc """
  Takes a Geometry and returns a base-16 encoded WKB string.

  The endian decides what the byte order will be.
  """
  @spec encode!(Geo.geometry(), Geo.endian()) :: binary
  def encode!(geom, endian \\ :xdr) do
    geom |> Encoder.encode!(endian) |> IO.iodata_to_binary() |> Base.encode16()
  end

  @doc """
  Takes a Geometry and returns a base-16 encoded WKB string.

  The endian decides what the byte order will be.
  """
  @spec encode(binary, Geo.endian()) :: {:ok, binary} | {:error, Exception.t()}
  def encode(geom, endian \\ :xdr) do
    {:ok, encode!(geom, endian)}
  rescue
    exception ->
      {:error, exception}
  end

  @doc """
  Takes a Geometry and returns WKB as iodata (a sequence of bytes).

  The endian decides what the byte order will be.
  """
  @spec encode_to_iodata(Geo.geometry(), Geo.endian()) :: iodata()
  def encode_to_iodata(geom, endian \\ :xdr) do
    Encoder.encode!(geom, endian)
  end

  @doc """
  Takes a WKB, either as a base-16 encoded string or a binary, and returns a Geometry.
  """
  @spec decode(binary) :: {:ok, Geo.geometry()} | {:error, Exception.t()}
  def decode(wkb) do
    {:ok, decode!(wkb)}
  rescue
    exception ->
      {:error, exception}
  end

  @doc """
  Takes a WKB, either as a base-16 encoded string or a binary, and returns a Geometry.
  """
  @spec decode!(binary) :: Geo.geometry()
  def decode!(wkb)

  def decode!("00" <> _ = wkb) do
    wkb
    |> Base.decode16!()
    |> decode!()
  end

  def decode!("01" <> _ = wkb) do
    wkb
    |> Base.decode16!()
    |> decode!()
  end

  def decode!(wkb) do
    Decoder.decode(wkb) |> elem(0)
  end
end