Packages
dynamic_rtree
0.2.0
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.
Retired package: Renamed - Package name was changed, look for 'ddrt' hex package to get it. https://hex.pm/packages/ddrt
Current section
Files
Jump to
Current section
Files
lib/drtree/elixir_rtree.ex
defmodule ElixirRtree do
alias ElixirRtree.Node
alias ElixirRtree.Utils
require Logger
import IO.ANSI
@moduledoc false
# Entre 1 y 64800. Bigger value => ^ updates speed, ~v query speed.
@max_area 20000
def new(opts) do
{f,s} = :rand.seed(:exrop,opts[:seed])
{node,new_ticket} = Node.new(f,s)
tree_init = case opts[:type] do
Map -> %{}
MerkleMap -> %MerkleMap{}
end
tree = tree_init
|> opts[:type].put(:ticket,new_ticket)
|> opts[:type].put(:root,node)
|> opts[:type].put(node,{[],nil,[{0,0},{0,0}]})
{tree,%{params: opts, seeding: f}}
end
# Helpers
def get_root(rbundle) do
rbundle.tree |> rbundle[:type].get(:root)
end
def is_root?(rbundle,node) do
get_root(rbundle) == node
end
# Actions
def rinsert(rbundle,{id,_box} = leaf) do
if rbundle.tree |> rbundle[:type].get(id) do
if rbundle.verbose,do: Logger.debug(cyan() <>"["<>green()<>"Insertion"<>cyan()<>"] failed:" <> yellow() <> " [#{id}] " <> cyan() <> "already exists at tree." <> yellow() <> " [Tip]"<> cyan() <> " use " <> yellow() <>"update_leaf/3")
rbundle.tree
else
path = best_subtree(rbundle,leaf)
t1 = :os.system_time(:microsecond)
r = insertion(rbundle,path,leaf)
|> recursive_update(tl(path),leaf,:insertion)
t2 = :os.system_time(:microsecond)
if rbundle.verbose,do: Logger.debug(cyan() <>"["<>green()<>"Insertion"<>cyan()<>"] success: "<> yellow() <> "[#{id}]" <> cyan() <> " was inserted at" <> yellow() <>" ['#{hd(path)}']")
if rbundle.verbose,do: Logger.info(cyan() <>"["<>green()<>"Insertion"<>cyan()<>"] took" <> yellow() <> " #{t2-t1} µs")
r
end
end
def bquery(rbundle,box) do
t1 = :os.system_time(:microsecond)
r = find_match_leafs(rbundle,box,[get_root(rbundle)],[],[])
t2 = :os.system_time(:microsecond)
if rbundle.verbose,do: Logger.info(cyan() <> "["<>color(201)<>"Query"<>cyan()<>"] box " <> yellow() <> "#{box |> Kernel.inspect} " <> cyan() <> "took " <> yellow() <> "#{t2-t1} µs")
r
end
def bquery(rbundle,box,depth) do
find_match_depth(rbundle,box,[{get_root(rbundle),0}],[],depth)
end
def rdelete(rbundle,id) do
t1 = :os.system_time(:microsecond)
r = if rbundle.tree |> rbundle[:type].get(id) do
remove(rbundle,id)
else
rbundle.tree
end
t2 = :os.system_time(:microsecond)
if rbundle.verbose,do: Logger.info(cyan() <>"["<>color(124)<>"Delete"<>cyan()<>"] leaf "<>yellow()<>"[#{id}]"<>cyan()<>" took "<>yellow()<>"#{t2-t1} µs")
r
end
def rupdate_leaf(rbundle,id,{old_box,new_box} = boxes) do
if rbundle.tree |> rbundle[:type].get(id) do
t1 = :os.system_time(:microsecond)
r = update(rbundle,id,boxes)
t2 = :os.system_time(:microsecond)
if rbundle.verbose,do: Logger.info(cyan()<>"["<>color(195)<>"Update"<>cyan()<>"] "<>yellow()<>"[#{id}]"<>cyan()<>" from "<>yellow()<>"#{old_box |> Kernel.inspect}"<>cyan()<>" to "<>yellow()<>"#{new_box |> Kernel.inspect}"<>cyan()<>" took "<>yellow()<>"#{t2-t1} µs")
r
else
if rbundle.verbose,do: Logger.warn(cyan()<>"["<>color(195)<>"Update"<>cyan()<>"] "<>yellow()<>"[#{id}] doesn't exists"<>cyan())
rbundle.tree
end
end
# You dont need to know old_box but is a BIT slower
def rupdate_leaf(rbundle,id,new_box) do
rupdate_leaf(rbundle,id,{rbundle.tree |> rbundle[:type].get(id) |> Utils.tuple_value(:bbox),new_box})
end
# Internal actions
## Insert
# triple - S (Structure Swifty Shift)
def triple_s(rbundle,old_node,new_node,{id,box}) do
tuple_entry = {old_node_childs_update,_daddy,_bbox} = rbundle.tree |> rbundle[:type].get(old_node) |> (fn {n,d,b} -> {n -- [id],d,b} end).()
tree_update = rbundle.tree
|> rbundle[:type].update!(new_node, fn {ch,d,b} -> {[id] ++ ch,d,b} end)
|> rbundle[:type].update!(id,fn {ch,_d,b} -> {ch,new_node,b} end)
if length(old_node_childs_update) > 0 do
%{rbundle | tree: tree_update |> rbundle[:type].put(old_node,tuple_entry)} |> recursive_update(old_node,box,:deletion)
else
%{rbundle | tree: tree_update} |> remove(old_node)
end
end
defp insertion(rbundle,branch,{_id,_box} = leaf) do
tree_update = add_entry(rbundle,hd(branch),leaf)
childs = tree_update |> rbundle[:type].get(hd(branch)) |> Utils.tuple_value(:childs)
final_tree = if length(childs) > rbundle.width do
handle_overflow(%{rbundle | tree: tree_update},branch)
else
tree_update
end
%{rbundle | tree: final_tree}
end
defp add_entry(rbundle,node,{id,box} = _leaf) do
rbundle.tree
|> rbundle[:type].update!(node,fn {ch,daddy,b} -> {[id] ++ ch,daddy,Utils.combine_multiple([box,b])} end)
|> rbundle[:type].put(id,{:leaf,node,box})
end
defp handle_overflow(rbundle,branch) do
n = hd(branch)
{node_n,new_node} = split(rbundle,n)
treeck = rbundle.tree |> rbundle[:type].put(:ticket,new_node.next_ticket)
if is_root?(rbundle,n) do
{new_root,ticket} = Node.new(rbundle.seeding,treeck |> rbundle[:type].get(:ticket))
treeck = treeck |> rbundle[:type].put(:ticket,ticket)
root_bbox = Utils.combine_multiple([node_n.bbox,new_node.bbox])
treeck = treeck |> rbundle[:type].put(new_node.id,{new_node.childs,new_root,new_node.bbox})
|> rbundle[:type].replace!(node_n.id,{node_n.childs,new_root,node_n.bbox})
|> rbundle[:type].replace!(:root,new_root)
|> rbundle[:type].put(new_root,{[node_n.id,new_node.id],nil,root_bbox})
new_node.childs |> Enum.reduce(treeck,fn c,acc ->
acc |> rbundle[:type].update!(c,fn {ch,_d,b} -> {ch,new_node.id,b} end)
end)
else
parent = hd(tl(branch))
treeck = treeck
|> rbundle[:type].put(new_node.id,{new_node.childs,parent,new_node.bbox})
|> rbundle[:type].replace!(node_n.id,{node_n.childs,parent,node_n.bbox})
|> rbundle[:type].update!(parent,fn {ch,d,b} -> {[new_node.id] ++ ch,d,Utils.combine_multiple([b,new_node.bbox])} end)
updated_tree = new_node.childs |> Enum.reduce(treeck,fn c,acc ->
acc |> rbundle[:type].update!(c,fn {ch,_d,b} -> {ch,new_node.id,b} end)
end)
if length(updated_tree |> rbundle[:type].get(parent) |> elem(0)) > rbundle.width, do: handle_overflow(%{rbundle | tree: updated_tree},tl(branch)), else: updated_tree
end
end
defp split(rbundle,node) do
sorted_nodes = rbundle.tree
|> rbundle[:type].get(node)
|> Utils.tuple_value(:childs)
|> Enum.map(fn n ->
box = rbundle.tree |> rbundle[:type].get(n) |> Utils.tuple_value(:bbox)
{box |> Utils.middle_value,n,box}
end)
|> Enum.sort
|> Enum.map(fn {_x,y,z} -> {y,z} end)
{n_id,n_bbox} = sorted_nodes
|> Enum.slice(0..((rbundle.width/2) - 1 |> Kernel.trunc)) |> Enum.unzip
{dn_id,dn_bbox} = sorted_nodes
|> Enum.slice(((rbundle.width/2) |> Kernel.trunc)..length(sorted_nodes) - 1) |> Enum.unzip
{new_node,next_ticket} = Node.new(rbundle.seeding,rbundle.tree |> rbundle[:type].get(:ticket))
n_bounds = n_bbox |> Utils.combine_multiple
dn_bounds = dn_bbox |> Utils.combine_multiple
{%{id: node, childs: n_id, bbox: n_bounds},
%{id: new_node, childs: dn_id, bbox: dn_bounds, next_ticket: next_ticket}}
end
defp best_subtree(rbundle,leaf) do
find_best_subtree(rbundle,get_root(rbundle),leaf,[])
end
defp find_best_subtree(rbundle,root,{_id,box} = leaf,track) do
childs = rbundle.tree |> rbundle[:type].get(root) |> Utils.tuple_value(:childs)
if is_list(childs) and length(childs) > 0 do
winner = get_best_candidate(rbundle,childs,box)
new_track = [root] ++ track
find_best_subtree(rbundle,winner,leaf,new_track)
else
if is_atom(childs), do: track, else: [root] ++ track
end
end
defp get_best_candidate(rbundle,candidates,box) do
win_entry = candidates |> Enum.reduce_while(%{id: :not_id,cost: :infinity},fn c,acc ->
cbox = rbundle.tree |> rbundle[:type].get(c) |> Utils.tuple_value(:bbox)
if Utils.contained?(cbox,box) do
{:halt, %{id: c, cost: 0}}
else
enlargement = Utils.enlargement_area(cbox,box)
if enlargement < acc |> Map.get(:cost) do
{:cont, %{id: c, cost: enlargement}}
else
{:cont, acc}
end
end
end)
win_entry[:id]
end
## Query
defp find_match_leafs(rbundle,box,dig,leafs,flood) do
f = hd(dig)
tail = if length(dig) > 1, do: tl(dig), else: []
{content,_dad,fbox} = rbundle.tree |> rbundle[:type].get(f)
{new_dig,new_leafs,new_flood} = if Utils.overlap?(fbox,box) do
if is_atom(content) do
{tail,[f] ++ leafs,flood}
else
if Utils.contained?(box,fbox), do: {tail,leafs,[f] ++ flood}, else: {content ++ tail,leafs,flood}
end
else
{tail,leafs,flood}
end
if length(new_dig) > 0 do
find_match_leafs(rbundle,box,new_dig,new_leafs,new_flood)
else
new_leafs ++ explore_flood(rbundle,new_flood)
end
end
defp explore_flood(rbundle,flood) do
next_floor = flood |> Enum.flat_map(fn x ->
case rbundle.tree |> rbundle[:type].get(x) |> Utils.tuple_value(:childs) do
:leaf -> []
any -> any
end end)
if length(next_floor) > 0,do: explore_flood(rbundle,next_floor), else: flood
end
defp find_match_depth(rbundle,box,dig,leafs,depth) do
{f,cdepth} = hd(dig)
tail = if length(dig) > 1, do: tl(dig), else: []
{content,_dad,fbox} = rbundle.tree |> rbundle[:type].get(f)
{new_dig,new_leafs} = if Utils.overlap?(fbox,box) do
if cdepth < depth and is_list(content) do
childs = content |> Enum.map(fn c -> {c,cdepth + 1} end)
{childs ++ tail,leafs}
else
{tail, [f] ++ leafs}
end
else
{tail,leafs}
end
if length(new_dig) > 0, do: find_match_depth(rbundle,box,new_dig,new_leafs,depth), else: new_leafs
end
## Delete
defp remove(rbundle,id) do
{_ch,parent,removed_bbox} = rbundle.tree |> rbundle[:type].get(id)
if parent do
tree_updated = rbundle.tree
|> rbundle[:type].delete(id)
|> rbundle[:type].update!(parent,fn {ch,daddy,b} -> {ch -- [id],daddy,b} end)
parent_childs = tree_updated |> rbundle[:type].get(parent) |> elem(0)
if length(parent_childs) > 0 do
%{rbundle | tree: tree_updated} |> recursive_update(parent,removed_bbox,:deletion)
else
remove(%{rbundle | tree: tree_updated},parent)
end
else
rbundle.tree |> rbundle[:type].update!(id,fn {ch,daddy,_b} -> {ch,daddy,[{0,0},{0,0}]} end)
end
end
## Hard update
defp update(rbundle,id,{old_box,new_box}) do
parent = rbundle.tree |> rbundle[:type].get(id) |> Utils.tuple_value(:dad)
parent_box = rbundle.tree |> rbundle[:type].get(parent) |> Utils.tuple_value(:bbox)
updated_tree = rbundle.tree |> rbundle[:type].update!(id, fn {ch,d,_b} -> {ch,d,new_box} end)
local_rbundle = %{rbundle | tree: updated_tree}
if Utils.contained?(parent_box,new_box) do
if Utils.in_border?(parent_box,old_box) do
if rbundle.verbose,do: Logger.debug(cyan()<>"["<>color(195)<>"Update"<>cyan()<>"] Good case: new box "<>yellow()<>"(#{new_box |> Kernel.inspect})"<>cyan()<>" of "<>yellow()<>"[#{id}]"<>cyan()<>" reduce the parent "<>yellow()<>"(['#{parent}'])"<>cyan()<>" box")
local_rbundle |> recursive_update(parent,old_box,:deletion)
else
if rbundle.verbose,do: Logger.debug(cyan()<>"["<>color(195)<>"Update"<>cyan()<>"] Best case: new box "<>yellow()<>"(#{new_box |> Kernel.inspect})"<>cyan()<>" of "<>yellow()<>"[#{id}]"<>cyan()<>" was contained by his parent "<>yellow()<>"(['#{parent}'])")
local_rbundle.tree
end
else
case local_rbundle |> node_brothers(parent) |> (fn b -> good_slot?(local_rbundle,b,new_box) end).() do
{new_parent,_new_brothers,_new_parent_box} ->
if rbundle.verbose,do: Logger.debug(cyan()<>"["<>color(195)<>"Update"<>cyan()<>"] Neutral case: new box "<>yellow()<>"(#{new_box |> Kernel.inspect})"<>cyan()<>" of "<>yellow()<>"[#{id}]"<>cyan()<>" increases the parent box but there is an available slot at one uncle "<>yellow()<>"(['#{new_parent}'])")
triple_s(local_rbundle,parent,new_parent,{id,old_box})
nil -> if Utils.area(parent_box) >= @max_area do
if rbundle.verbose,do: Logger.debug(cyan()<>"["<>color(195)<>"Update"<>cyan()<>"] Worst case: new box "<>yellow()<>"(#{new_box |> Kernel.inspect})"<>cyan()<>" of "<>yellow()<>"[#{id}]"<>cyan()<>" increases the parent box which was so big "<>yellow()<>"#{(((Utils.area(parent_box) |> Kernel.trunc)/@max_area) * 100) |> Kernel.trunc } %. "<>cyan()<>"So we proceed to delete "<>yellow()<>"[#{id}]"<>cyan()<>" and reinsert at tree")
local_rbundle |> top_down({id,new_box})
else
if rbundle.verbose,do: Logger.debug(cyan()<>"["<>color(195)<>"Update"<>cyan()<>"] Bad case: new box "<>yellow()<>"(#{new_box |> Kernel.inspect})"<>cyan()<>" of "<>yellow()<>"[#{id}]"<>cyan()<>" increases the parent box which isn't that big yet "<>yellow()<>"#{(((Utils.area(parent_box) |> Kernel.trunc)/@max_area) * 100) |> Kernel.trunc} %. "<>cyan()<>"So we proceed to increase parent "<>yellow()<>"(['#{parent}'])"<>cyan()<>" box")
local_rbundle |> recursive_update(parent,new_box,:insertion)
end
end
end
end
## Common updates
defp top_down(rbundle,{id,box}) do
%{rbundle | tree: rbundle |> remove(id)} |> rinsert({id,box})
end
# Recursive bbox updates when you have node path from root (at insertion)
defp recursive_update(rbundle,path,{_id,box} = leaf,:insertion)when length(path) > 0 do
{modified,t} = update_node_bbox(rbundle,hd(path),box,:insertion)
if modified and length(path) > 1, do: recursive_update(%{rbundle | tree: t},tl(path),leaf,:insertion), else: rbundle.tree
end
# Recursive bbox updates when u dont have node path from root, so you have to query parents map... (at delete)
defp recursive_update(rbundle,node,box,mode)when is_list(node) |> Kernel.not do
{modified,t} = update_node_bbox(rbundle,node,box,mode)
next = rbundle.tree |> rbundle[:type].get(node) |> Utils.tuple_value(:dad)
if modified and next, do: recursive_update(%{rbundle | tree: t},next,box,mode), else: t
end
# Typical dumbass safe method
defp recursive_update(rbundle,_path,_leaf,:insertion) do
rbundle.tree
end
defp update_node_bbox(rbundle,node,the_box,action) do
node_box = rbundle.tree |> rbundle[:type].get(node) |> Utils.tuple_value(:bbox)
new_bbox = case action do
:insertion -> Utils.combine(node_box,the_box)
:deletion -> if Utils.in_border?(node_box,the_box) do
rbundle.tree
|> rbundle[:type].get(node)
|> Utils.tuple_value(:childs)
|> Enum.map(fn c ->
rbundle.tree |> rbundle[:type].get(c) |> Utils.tuple_value(:bbox) end)
|> Utils.combine_multiple
else
node_box
end
end
bbox_mutation(rbundle,node,new_bbox,node_box)
end
defp bbox_mutation(rbundle,node,new_bbox,node_box) do
if new_bbox == node_box do
{false,rbundle.tree}
else
t = rbundle.tree |> rbundle[:type].update!(node, fn {ch,d,_b} -> {ch,d,new_bbox} end)
{true,t}
end
end
# Return the brothers of the node [{brother_id, brother_childs, brother_box},...]
defp node_brothers(rbundle,node) do
parent = rbundle.tree |> rbundle[:type].get(node) |> Utils.tuple_value(:dad)
rbundle.tree
|> rbundle[:type].get(parent)
|> Utils.tuple_value(:childs)
|> (fn c -> if c, do: c -- [node], else: [] end).()
|> Enum.map(fn b ->
tuple = rbundle.tree |> rbundle[:type].get(b)
{b,tuple |> Utils.tuple_value(:childs),tuple |> Utils.tuple_value(:bbox)} end)
end
# Find a good slot (at bros/brothers list) for the box, it means that the brother hasnt the max childs and the box is at the limits of his own
defp good_slot?(rbundle,bros,box) do
bros |> Enum.find(fn {_bid,bchilds,bbox} -> length(bchilds) < rbundle.width and Utils.contained?(bbox,box) end)
end
end