Packages

Distributed Dynamic R-tree (DDRT) implementation for Elixir. It's mainly a R-tree. Why dynamic? Because it's optimized to do fast updates at the tree leafs spatial index. Why distributed? Well.. you can run the DDRT on different nodes and they will have the same r-tree data.

Current section

Files

Jump to
dynamic_rtree lib drtree.ex
Raw

lib/drtree.ex

defmodule Drtree do
@moduledoc """
This is the API module of the elixir r-tree implementation where you can do the basic actions.
## Actions provided:
- insert/2
- query/2
- query/3
- delete/2
- update_leaf/3
- execute/1
## Important points:
Every `id` inserted must be uniq, `Drtree` won't crush the duplicated `id`.
Every bounding box should look like this: `[{xm,xM},{ym,yM}]`
- `xm`: minimum x value
- `xM`: maximum x value
- `ym`: minimum y value
- `yM`: maximum y value
"""
@type coord_range :: {number(),number()}
@type bounding_box :: list(coord_range())
@doc """
Insert the given `leaf` to the given `tree`.
Returns `map()`
## Parameters
- `tree`: Map that represents the r-tree structure
- `{id, bounding box}` = `leaf`: Tuple with spatial data to insert in the r-tree.
## Examples
iex> t = Drtree.new
iex> t = t |> Drtree.insert({0,[{1,2},{5,6}]})
%{
0 => :leaf,
43143342109176739 => [0],
:metadata => %{...},
:parents => %{0 => 43143342109176739},
:ticket => [19125803434255161 | 82545666616502197],
'root' => 43143342109176739
}
"""
@spec insert(map(),tuple())::map()
defdelegate insert(tree,leaf), to: ElixirRtree
@doc """
Find all leafs that match with the `box` query.
Returns `list()`
## Parameters
- `tree`: Map that represents the r-tree structure.
- `box`: Bounding box `[{x_min,x_max},{y_min,y_max}]`.
## Examples
iex> t = Drtree.new
iex> t = t |> Drtree.insert({0,[{10,20},{0,20}]})
iex> t |> Drtree.query([{-100,100},{-50,50}])
[0]
iex> t |> Drtree.query([{-100,100},{30,50}])
[]
iex> t |> Drtree.query([{15,100},{10,50}])
[0]
iex> t |> Drtree.query([{15,16},{10,11}])
[0]
"""
@spec query(map(),bounding_box)::list(integer())
defdelegate query(tree,box), to: ElixirRtree
@doc """
Find all nodes that match with the `box` query at the given `depth` of the r-tree.
Returns `list()`
## Parameters
- `tree`: Map that represents the r-tree structure.
- `box`: Bounding box `[{x_min,x_max},{y_min,y_max}]`.
- `depth`: Integer that define the query r-tree depth limit. Note: 0 is root node.
"""
@spec query(map(),bounding_box,integer())::list(integer())
defdelegate query(tree,box,depth), to: ElixirRtree
@doc """
Delete the leaf with the given `id`.
Returns `map()`
## Parameters
- `tree`: Map that represents the r-tree structure.
- `id`: Id of the required leaf to erase.
## Examples
iex> t = Drtree.new
%{
43143342109176739 => [],
:metadata => %{...},
:parents => %{},
:ticket => [19125803434255161 | 82545666616502197],
'root' => 43143342109176739
}
iex> t = t |> Drtree.insert({0,[{10,20},{0,20}]})
%{
0 => :leaf,
43143342109176739 => [0],
:metadata => %{...},
:parents => %{0 => 43143342109176739},
:ticket => [19125803434255161 | 82545666616502197],
'root' => 43143342109176739
}
iex> t = t |> Drtree.delete(0)
%{
43143342109176739 => [],
:metadata => %{...},
:parents => %{},
:ticket => [19125803434255161 | 82545666616502197],
'root' => 43143342109176739
}
"""
@spec delete(map(),any())::map()
defdelegate delete(tree,id), to: ElixirRtree
@doc """
Update the leaf bounding box with the given `id`.
Returns `map()`
## Parameters
- `tree`: Map that represents the r-tree structure.
- `id`: Id of the required leaf to be updated.
- `update`: Bounding box update. `{old_box,new_box}` | `{new_box}`
## Examples
iex> t = Drtree.new
iex> t = t |> Drtree.insert({0,[{10,20},{0,20}]})
%{
...
}
iex> t = t |> Drtree.update_leaf(0,[{11,21},{1,21}])
%{
...
}
iex> t = t |> Drtree.update_leaf(0,{[{10,20},{0,20}],[{11,21},{1,21}]})
%{
...
}
"""
@spec update_leaf(map(),any(),{bounding_box,bounding_box} | {bounding_box})::map()
defdelegate update_leaf(tree,id,update), to: ElixirRtree
@doc """
Execute the `tree` RAM resources.
Returns `boolean()`
## Parameters
- `tree`: Map that represents the r-tree structure.
## Examples
iex> t = Drtree.new
iex> t |> Drtree.execute
true
"""
@spec execute(map())::boolean()
defdelegate execute(tree), to: ElixirRtree
@opt_values %{
type: [:standalone,:merkle,:distributed],
access: [:protected, :public, :private]
}
@defopts %{
width: 6,
type: :standalone,
verbose: false, #TODO: This crazy american prefers Logger comparison than the verbose flag ùwú
database: false,
access: :protected,
seed: 0
}
@doc """
Create a new r-tree with default parameters.
Returns `map()`
## Examples
iex> Drtree.new
%{
43143342109176739 => [],
:metadata => %{...},
:parents => %{},
:ticket => [19125803434255161 | 82545666616502197],
'root' => 43143342109176739
}
"""
@spec new()::map()
def new()do
ElixirRtree.new(@defopts)
end
@doc """
Create a new r-tree with the given `opts`.
Returns `map()`
## Parameters
- `width`: the node childs limit (the amplitude)
- `database`: the boolean flag that set the r-tree to a dgraph database (performance -825%)
- `verbose`: the boolean flag that set the logger output (performance -10%)
- `access`: the access permissions to the tree `[:public, :protected, :private]`
- `seed`: the seed for the r-tree middle nodes uuid
## Examples
iex> Drtree.new(%{access: :public, width: 8})
%{
43143342109176739 => [],
:metadata => %{
dgraph: nil,
ets_table: #Reference<0.2361800908.1666318340.51022>,
params: %{
access: :public,
database: false,
seed: 0,
type: :standalone,
verbose: false,
width: 8
},
seeding: %{
...
}
},
:parents => %{},
:ticket => [19125803434255161 | 82545666616502197],
'root' => 43143342109176739
}
"""
@spec new(%{})::map()
def new(opts)do
good_keys = opts |> Map.keys |> Enum.filter(fn k -> constraints() |> Map.has_key?(k) and constraints()[k].(opts[k]) end)
good_keys |> Enum.reduce(@defopts, fn k,acc ->
acc |> Map.put(k,opts[k])
end) |> ElixirRtree.new
end
@doc false
def default_params()do
@defopts
end
defp constraints()do
%{
width: fn v -> v > 0 end,
type: fn v -> v in (@opt_values |> Map.get(:type)) end,
verbose: fn v -> is_boolean(v) end,
database: fn v -> is_boolean(v) end,
access: fn v -> v in (@opt_values |> Map.get(:access)) end,
seed: fn v -> is_integer(v) end
}
end
end