# `TzWorld.Backend.SpatialIndex`
[🔗](https://github.com/kipcole9/tz_world/blob/v2.5.0/lib/tz_world/backend/spatial_index.ex#L1)

Resolves timezones from a coordinate using an R-tree spatial index
held in `:persistent_term`.

This is the recommended backend and is also the default — applications
that do not pin `:default_backend` will pick it up automatically as
long as it is started in the supervision tree.

## How it works

At startup the backend reads the compressed timezone shape data that
`mix tz_world.update` installs and builds two structures:

* a Sort-Tile-Recursive packed R-tree over every shape's bounding
  box, with one entry per sub-polygon of a `Geo.MultiPolygon`;

* each shape compiled by `TzWorld.PackedGeometry`: its rings packed
  into binaries, with their edges indexed by horizontal band.

Both are stored with the data version as a single term under one
`:persistent_term` key. A lookup finds candidate shapes in the
R-tree, then tests each against only the ring edges in the query
point's band — typically a few dozen, even in rings with nearly
200,000 vertices — so it takes microseconds.

Lookups (`TzWorld.timezone_at/1` and `TzWorld.all_timezones_at/1`)
read the stored term directly. They do not go through the GenServer
mailbox and do not copy any term, which makes them lock-free and
safe to call from any number of processes concurrently.

## Reload

`reload_timezone_data/0` rebuilds the index and replaces the stored
term in one `:persistent_term.put/2`. Lookups carry on against the
previous index until then, so a reload never blocks them. Loading
and reloading compile the shapes in parallel, one task per shape, and
take a couple of seconds on a multi-core machine and several on a
single core. The put also triggers a global garbage collection for
every process that references a persistent term — fine when reload
is invoked after a data update, costly on a hot path. Reload should
not be called on every request.

## Memory profile

About 150 MB for the data without ocean coverage, the default. Ring
vertices are held as packed binaries at 16 bytes a vertex, and those
binaries live off-heap: `:persistent_term.info/0` reports only the
small term that refers to them, so measure `:erlang.memory/1`
instead.

## Public API

In normal use you will not call functions on this module directly —
use `TzWorld.timezone_at/1`, `TzWorld.all_timezones_at/1`,
`TzWorld.version/0`, and `TzWorld.reload_timezone_data/0`. Add this
module to your supervision tree to make it the running backend:

    children = [
      TzWorld.Backend.SpatialIndex
    ]

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `start_link`

Starts the backend and loads the time zone data.

The load is performed synchronously inside `init/1`, so once
`start_link/1` returns the index is in `:persistent_term` and
lookups are immediately ready to serve. The backend is registered
under its module name.

### Arguments

* `options` is a keyword list of options.

### Options

* There are none. `options` is accepted so the module can be used
  as a child specification, and is otherwise ignored.

### Returns

* `{:ok, pid}` once the data is loaded. If the data has not been
  installed the backend still starts and logs a warning, and lookups
  return `{:error, :enoent}` until the data is installed with
  `mix tz_world.update` and loaded with
  `TzWorld.reload_timezone_data/0`.

* `{:error, {:already_started, pid}}` if the backend is already
  running.

* `{:error, reason}` if the installed data could not be decoded.

### Examples

Add the backend to an application's supervision tree:

    children = [
      TzWorld.Backend.SpatialIndex
    ]

    Supervisor.start_link(children, strategy: :one_for_one)

---

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