Skip to content

Commit

Permalink
Merge pull request #8 from kipcole9/master
Browse files Browse the repository at this point in the history
Refactor separate backends
  • Loading branch information
kipcole9 authored May 12, 2020
2 parents fa6d951 + 2848636 commit 32fb9ab
Show file tree
Hide file tree
Showing 18 changed files with 1,002 additions and 125 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ erl_crash.dump
/priv/timezones-geodata.etf
/priv/timezones-geodata.etf.zip
/priv/timezones-geodata.json.zip
/priv/timezones-geodata.dets
TIMEZONES_GEOJSON_VERSION
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
# Changelog for Tz_World v0.4.0

This is the changelog for Tz_World v0.4.0 released on May 12th, 2020. For older changelogs please consult the release tag on [GitHub](https://github.com/kimlai/tz_world/tags)

* Adds configurable backends. Each backend is a GenServer that must be added to an applications supervision tree or started manually.

### Breaking change

* When specifying a `lng`, `lat` to `TzWorld.timezone_at/2` the coordinates must be wrapped in a tuple. For example `TzWorld.timezone_at({3.2, 45.32})` making it consistent with the `Geo.Point` and `Geo.PointZ` strategies.

### Configurable backends

* `TzWorld.Backend.Memory` which retains all data in memory for fastest performance at the expense of using approximately 1Gb of memory
* `TzWorld.Backend.Dets` which uses Erlang's `:dets` data store. This uses negligible memory at the expense of slow access times (approximaltey 500ms in testing)
* `TzWorld.Backend.DetsWithIndexCache` which balances memory usage and performance. This backend is recommended in most situations since its performance is similar to `TzWorld.Backend.Memory` (about 5% slower in testing) and uses about 25Mb of memory
* `TzWorld.Backend.Ets` which uses `:ets` for storage. With the default settings of `:compressed` for the `:ets` table its memory consumption is about 512Mb but with access that is over 20 times slower than `TzWorld.Backend.DetsWithIndexCache`
* `TzWorld.Backend.EtsWithIndexCache` which uses `:ets` for storage with an additional in-memory cache of the bounding boxes. This still uses about 512Mb but is faster than any of the other backends by about 40%

### Enhancements

* Add `TzWorld.all_timezones_at/2` to return all timezones for a given location. In rare cases, usually disputed territory, multiple timezones may be declared for a overlapping regions. `TzWorld.all_timezones_at/2` returns a (potentially empty) list of all time zones knowns for a given point. *Futher testing of this function is required and will be completed before version 1.0*.

# Changelog for Tz_World v0.3.0

This is the changelog for Tz_World v0.3.0 released on February 16th, 2020. For older changelogs please consult the release tag on [GitHub](https://github.com/kimlai/tz_world/tags)
Expand Down Expand Up @@ -42,6 +64,8 @@ This is the changelog for Tz_World v0.2.0 released on November 28th, 2019. For

* Added SRID to the GeoJSON

* Updated package and ran dialyzer

* Added a config option :data_dir specifies the location of the compressed etf. Default is `./priv`

* Updated README, package and docs
Expand Down
50 changes: 35 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,48 +9,68 @@ Add `tz_world` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:tz_world, "~> 0.2.0"}
{:tz_world, "~> 0.4.0"}
]
end
```

After adding TzWorld as a dependency, run `mix deps.get` to install it. Then run `mix tz_world.update` to install the timezone data.
After adding `TzWorld` as a dependency, run `mix deps.get` to install it. Then run `mix tz_world.update` to install the timezone data.

**NOTE** No data is installed with the package and until the data is installed with `mix tz_world.update` all calls to `TzWorld.timezone_at/1` will return `{:error, :noent}`.
**NOTE** No data is installed with the package and until the data is installed with `mix tz_world.update` all calls to `TzWorld.timezone_at/1` will return `{:error, :time_zone_not_found}`.

## Installing the Timezones Geo JSON data
## Backend selection

Installing `tz_world` from source or from hex does not include the timezones geo JSON data. The data is requried and to install or update it run:
`TzWorld` provides alternative strategies for managing access to the backend data. Each backend is implemented as a `GenServer` that needs to be either manually started with `BackendModule.start_link/1` or preferably added to your application's supervision tree.

For example:
```elixir
mix tz_world.update
defmodule MyApp.Application do
@moduledoc false

use Application

def start(_type, _args) do
children = [
...
TzWorld.Backend.DetsWithIndexCache
]

opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
end
```
This task will download, transform, zip and store the timezones geo data. Depending on internet and computer speed this may take a few minutes.
The following backends are available:

### Data location
* `TzWorld.Backend.Memory` which retains all data in memory for fastest performance at the expense of using approximately 1Gb of memory
* `TzWorld.Backend.Dets` which uses Erlang's `:dets` data store. This uses negligible memory at the expense of slow access times (approximaltey 500ms in testing)
* `TzWorld.Backend.DetsWithIndexCache` which balances memory usage and performance. This backend is recommended in most situations since its performance is similar to `TzWorld.Backend.Memory` (about 5% slower in testing) and uses about 25Mb of memory
* `TzWorld.Backend.Ets` which uses `:ets` for storage. With the default settings of `:compressed` for the `:ets` table its memory consumption is about 512Mb but with access that is over 20 times slower than `TzWorld.Backend.DetsWithIndexCache`
* `TzWorld.Backend.EtsWithIndexCache` which uses `:ets` for storage with an additional in-memory cache of the bounding boxes. This still uses about 512Mb but is faster than any of the other backends by about 40%

By default the data will be placed in `./priv/tz_world`.
## Installing the Timezones Geo JSON data

An alternative location can be configured in `config.exs` as follows:
Installing `tz_world` from source or from hex does not include the timezones geo JSON data. The data is requried and to install or update it run:
```elixir
config :tz_world,
data_dir: "some/directory"
mix tz_world.update
```
This task will download, transform, zip and store the timezones geo data. Depending on internet and computer speed this may take a few minutes.

### Updating the Timezone data

From time-to-time the timezones geo JSON data is updated in the [upstream project](https://github.com/evansiroky/timezone-boundary-builder/releases). The mix task `mix tz_world.update` will update the data if it is available. This task can be run at any time, it will detect when new data is available and only download it when a new release is available. The generated file `TIMEZONES_GEOJSON_VERSION` is used to track the current installed version of the data.
From time-to-time the timezones geo JSON data is updated in the [upstream project](https://github.com/evansiroky/timezone-boundary-builder/releases). The mix task `mix tz_world.update` will update the data if it is available. This task can be run at any time, it will detect when new data is available and only download it when a new release is available.

A running application can also be instructed to reload the data by executing `TzWorld.reload_timezone_data`.

## Usage

The primary API is `TzWorld.timezone_at`. It takes either a `Geo.Point` struct or a `longitude` and `latitude` in degrees. Note the parameter order: `longitude`, `latitude`.
The primary API is `TzWorld.timezone_at`. It takes either a `Geo.Point` struct or a `longitude` and `latitude` in degrees. Note the parameter order: `longitude`, `latitude`. It also takes and optional second parameter, `backend`, which must be one of the configured and running backend modules. By default `timezone_at/2` will detect a running backend and will raise an exception if no running backend is found.

```elixir
iex> TzWorld.timezone_at(%Geo.Point{coordinates: {3.2, 45.32}})
{:ok, "Europe/Paris"}

iex> TzWorld.timezone_at(3.2, 45.32)
iex> TzWorld.timezone_at({3.2, 45.32})
{:ok, "Europe/Paris"}

iex> TzWorld.timezone_at(%Geo.PointZ{coordinates: {-74.006, 40.7128, 0.0}})
Expand Down
23 changes: 23 additions & 0 deletions benchee/backend.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
point = %Geo.Point{coordinates: {3.2, 45.32}}
TzWorld.Backend.Memory.start_link
TzWorld.Backend.Ets.start_link
TzWorld.Backend.EtsWithIndexCache.start_link
TzWorld.Backend.Dets.start_link
TzWorld.Backend.DetsWithIndexCache.start_link

Benchee.run(
%{
"Backend Memory" => fn ->
TzWorld.timezone_at(point, TzWorld.Backend.Memory) end,
"Backend Ets" => fn ->
TzWorld.timezone_at(point, TzWorld.Backend.Ets) end,
"Backend EtsWithIndexCache" => fn ->
TzWorld.timezone_at(point, TzWorld.Backend.EtsWithIndexCache) end,
"Backend Dets" => fn ->
TzWorld.timezone_at(point, TzWorld.Backend.Dets) end,
"Backend DetsWithIndexCache" => fn ->
TzWorld.timezone_at(point, TzWorld.Backend.DetsWithIndexCache) end,
},
time: 10,
memory_time: 2
)
14 changes: 0 additions & 14 deletions lib/application.ex

This file was deleted.

2 changes: 2 additions & 0 deletions lib/mix/tasks/update_timezones_geojson.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ defmodule Mix.Tasks.TzWorld.Update do

def run(_args) do
Application.ensure_all_started(:tz_world)
TzWorld.Backend.Memory.start_link
TzWorld.Backend.Dets.start_link

case Downloader.current_release() do
{:ok, current_release} ->
Expand Down
Loading

0 comments on commit 32fb9ab

Please sign in to comment.