# `ExCodecs.Spatial.Metadata`
[🔗](https://github.com/AnimaLogica/codecs/blob/v0.2.4/lib/ex_codecs/spatial/metadata.ex#L1)

Free-form metadata for spatial clouds.

## Fields

  * `:entries` — `%{optional(String.t()) => term()}` arbitrary format or
    application values; defaults to `%{}`. String keys are the public API
    convention.
  * `:comments` — `[String.t()]` ordered human-readable comments; defaults
    to `[]`. No encoding beyond Elixir UTF-8 string conventions is imposed.
  * `:source` — `String.t() | nil` source file, URI, sensor, or producer
    identifier; defaults to `nil`.
  * `:created_at` — `DateTime.t() | nil` creation instant; defaults to `nil`.
    Use a timezone-aware `DateTime`, conventionally UTC.

## Example

    iex> created_at = ~U[2026-07-16 12:00:00Z]
    iex> ExCodecs.Spatial.Metadata.new(
    ...>   entries: %{"coordinate_system" => "ENU"},
    ...>   comments: ["Captured after calibration"],
    ...>   source: "sensor://roof-lidar",
    ...>   created_at: created_at
    ...> )
    %ExCodecs.Spatial.Metadata{
      entries: %{"coordinate_system" => "ENU"},
      comments: ["Captured after calibration"],
      source: "sensor://roof-lidar",
      created_at: ~U[2026-07-16 12:00:00Z]
    }

# `t`

```elixir
@type t() :: %ExCodecs.Spatial.Metadata{
  comments: [String.t()],
  created_at: DateTime.t() | nil,
  entries: %{optional(String.t()) =&gt; term()},
  source: String.t() | nil
}
```

Spatial metadata. See the module documentation for every field, its default,
units or conventions, and a construction example.

# `add_comment`

```elixir
@spec add_comment(t(), String.t()) :: t()
```

Appends a comment string.

## Arguments

  * `meta` — `%Metadata{}`
  * `comment` — `String.t()` appended after existing comments.

## Returns

Updated `%Metadata{}`

## Raises

* `FunctionClauseError` unless `meta` is `%Metadata{}` and `comment` is a
  binary.
* `ArgumentError` if a manually constructed metadata value has an improper
  `:comments` list.

## Examples

    iex> m = ExCodecs.Spatial.Metadata.add_comment(ExCodecs.Spatial.Metadata.new(), "x")
    iex> m.comments
    ["x"]

# `get`

```elixir
@spec get(t(), String.t(), term()) :: term()
```

Fetches an entry by string key.

## Arguments

  * `meta` — `%Metadata{}`
  * `key` — `String.t()` to look up.
  * `default` — `term()` returned when the key is absent; defaults to `nil`.

## Returns

Stored value or `default`

## Raises

* `FunctionClauseError` unless `meta` is `%Metadata{}` and `key` is a binary.
* `BadMapError` if a manually constructed metadata value has a non-map
  `:entries` field.

## Examples

    iex> ExCodecs.Spatial.Metadata.get(ExCodecs.Spatial.Metadata.new(), "missing", :nope)
    :nope

# `new`

```elixir
@spec new(keyword()) :: t()
```

Builds metadata from options.

## Arguments

  * `opts` — a keyword list with:
    * `:entries` — an enumerable accepted by `Map.new/1`, conventionally a
      map with string keys; defaults to `%{}`.
    * `:comments` — `[String.t()]`; defaults to `[]`.
    * `:source` — `String.t() | nil`; defaults to `nil`.
    * `:created_at` — `DateTime.t() | nil`; defaults to `nil`.

## Returns

`%Metadata{}`

## Raises

* `FunctionClauseError` if `opts` is not a keyword list.
* `Protocol.UndefinedError` if `:entries` is not enumerable.
* `ArgumentError` if an enumerable entry cannot be converted to a map pair.

## Examples

    iex> m = ExCodecs.Spatial.Metadata.new(comments: ["hi"], entries: %{"k" => 1})
    iex> m.comments
    ["hi"]

# `put`

```elixir
@spec put(t(), String.t(), term()) :: t()
```

Puts a string key into `entries`.

## Arguments

  * `meta` — `%Metadata{}`
  * `key` — `String.t()` key.
  * `value` — `term()` to store, replacing an existing value.

## Returns

Updated `%Metadata{}`

## Raises

* `FunctionClauseError` unless `meta` is `%Metadata{}` and `key` is a binary.
* `BadMapError` if a manually constructed metadata value has a non-map
  `:entries` field.

## Examples

    iex> m = ExCodecs.Spatial.Metadata.put(ExCodecs.Spatial.Metadata.new(), "a", 1)
    iex> ExCodecs.Spatial.Metadata.get(m, "a")
    1

---

*Consult [api-reference.md](api-reference.md) for complete listing*
